dexverse-cli 0.1.3.post2__tar.gz → 0.1.4rc2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. dexverse_cli-0.1.4rc2/PKG-INFO +300 -0
  2. {dexverse_cli-0.1.3.post2 → dexverse_cli-0.1.4rc2}/pyproject.toml +8 -7
  3. dexverse_cli-0.1.4rc2/src/dexverse_cli/README.md +285 -0
  4. dexverse_cli-0.1.4rc2/src/dexverse_cli/__init__.py +1 -0
  5. dexverse_cli-0.1.4rc2/src/dexverse_cli/cli.py +236 -0
  6. dexverse_cli-0.1.4rc2/src/dexverse_cli/common/__init__.py +24 -0
  7. dexverse_cli-0.1.4rc2/src/dexverse_cli/common/hashes.py +87 -0
  8. {dexverse_cli-0.1.3.post2/src/dexverse_cli → dexverse_cli-0.1.4rc2/src/dexverse_cli/common}/state.py +3 -3
  9. dexverse_cli-0.1.4rc2/src/dexverse_cli/download/__init__.py +16 -0
  10. dexverse_cli-0.1.4rc2/src/dexverse_cli/download/batch.py +246 -0
  11. dexverse_cli-0.1.4rc2/src/dexverse_cli/download/filesystem.py +190 -0
  12. {dexverse_cli-0.1.3.post2/src/dexverse_cli → dexverse_cli-0.1.4rc2/src/dexverse_cli/download}/http.py +60 -9
  13. {dexverse_cli-0.1.3.post2/src/dexverse_cli → dexverse_cli-0.1.4rc2/src/dexverse_cli/download}/manifest.py +107 -25
  14. dexverse_cli-0.1.4rc2/src/dexverse_cli/download/runner.py +110 -0
  15. dexverse_cli-0.1.4rc2/src/dexverse_cli/download/transfer.py +274 -0
  16. dexverse_cli-0.1.4rc2/src/dexverse_cli/progress/__init__.py +25 -0
  17. dexverse_cli-0.1.4rc2/src/dexverse_cli/progress/bar.py +144 -0
  18. dexverse_cli-0.1.4rc2/src/dexverse_cli/progress/formatting.py +58 -0
  19. dexverse_cli-0.1.4rc2/src/dexverse_cli/progress/reporter.py +434 -0
  20. dexverse_cli-0.1.4rc2/src/dexverse_cli/progress/view.py +113 -0
  21. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/__init__.py +15 -0
  22. {dexverse_cli-0.1.3.post2/src/dexverse_cli → dexverse_cli-0.1.4rc2/src/dexverse_cli/restore}/alignment.py +1 -1
  23. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/batch.py +116 -0
  24. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/instruction.py +43 -0
  25. dexverse_cli-0.1.3.post2/src/dexverse_cli/unversioned_hdf5.py → dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/legacy.py +5 -25
  26. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/media.py +54 -0
  27. dexverse_cli-0.1.3.post2/src/dexverse_cli/transaction.py → dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/publish.py +17 -1
  28. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/runner.py +70 -0
  29. dexverse_cli-0.1.3.post2/src/dexverse_cli/hdf5_schema.py → dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/schema.py +2 -7
  30. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/session.py +43 -0
  31. dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/versioned.py +79 -0
  32. dexverse_cli-0.1.4rc2/src/dexverse_cli/update/__init__.py +1 -0
  33. dexverse_cli-0.1.4rc2/src/dexverse_cli/update/check.py +346 -0
  34. dexverse_cli-0.1.4rc2/src/dexverse_cli/update/gate.py +96 -0
  35. dexverse_cli-0.1.4rc2/src/dexverse_cli/update/observation.py +243 -0
  36. dexverse_cli-0.1.4rc2/src/dexverse_cli/update/upgrade.py +203 -0
  37. {dexverse_cli-0.1.3.post2 → dexverse_cli-0.1.4rc2}/uv.lock +3 -24
  38. dexverse_cli-0.1.3.post2/PKG-INFO +0 -125
  39. dexverse_cli-0.1.3.post2/src/dexverse_cli/README.md +0 -109
  40. dexverse_cli-0.1.3.post2/src/dexverse_cli/__init__.py +0 -1
  41. dexverse_cli-0.1.3.post2/src/dexverse_cli/cli.py +0 -194
  42. dexverse_cli-0.1.3.post2/src/dexverse_cli/download.py +0 -476
  43. dexverse_cli-0.1.3.post2/src/dexverse_cli/jetson_encoder.py +0 -168
  44. dexverse_cli-0.1.3.post2/src/dexverse_cli/pack.py +0 -167
  45. dexverse_cli-0.1.3.post2/src/dexverse_cli/unpack.py +0 -185
  46. {dexverse_cli-0.1.3.post2 → dexverse_cli-0.1.4rc2}/.gitignore +0 -0
  47. {dexverse_cli-0.1.3.post2 → dexverse_cli-0.1.4rc2}/.python-version +0 -0
  48. /dexverse_cli-0.1.3.post2/src/dexverse_cli/raw_schema.py → /dexverse_cli-0.1.4rc2/src/dexverse_cli/restore/layout.py +0 -0
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.5
2
+ Name: dexverse-cli
3
+ Version: 0.1.4rc2
4
+ Summary: Download DexVerse dataset manifests and unpack HDF5 sessions
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: av<17,>=14
7
+ Requires-Dist: h5py<4,>=3.11
8
+ Requires-Dist: httpx<1,>=0.28
9
+ Requires-Dist: numpy<3,>=1.26
10
+ Requires-Dist: packaging<27,>=24
11
+ Requires-Dist: pillow<13,>=10
12
+ Requires-Dist: rich<15,>=13.9
13
+ Requires-Dist: typer<1,>=0.16
14
+ Description-Content-Type: text/markdown
15
+
16
+ # dexverse-cli
17
+
18
+ dexverse-cli downloads DexVerse dataset manifests over HTTP(S) and unpacks
19
+ HDF5 sessions back to the restored dataset layout. It requires
20
+ Python 3.10 or newer.
21
+
22
+
23
+ ## Install
24
+
25
+ ```console
26
+ pip install dexverse-cli
27
+ ```
28
+
29
+ ## Automatic updates
30
+
31
+ Every business command (`download`, `unpack`, `convert`) first asks whether a
32
+ newer `dexverse-cli` is published. Public PyPI is asked first, because that is
33
+ where a new release appears immediately; when PyPI cannot be reached the check
34
+ falls back to the source this machine's pip is configured with (`pip.conf`,
35
+ `PIP_INDEX_URL`, mirrors, proxies). The check is bounded to about two seconds.
36
+
37
+ - Nothing new, or nothing to ask: the command runs as usual.
38
+ - A newer release exists: the CLI upgrades itself in place with
39
+ `python -m pip install --upgrade dexverse-cli` from the source that answered,
40
+ and then exits with status **20**. Run the same command again; the rerun uses
41
+ the new version.
42
+ - The check times out, pip is missing, no source answers, or the upgrade fails:
43
+ one line is printed and the requested command still runs.
44
+
45
+ The switch is on by default. Turn the check off for one command, or permanently:
46
+
47
+ ```console
48
+ dexverse-dl download --manifest manifest.json --out ./dataset --no-update-check
49
+ set DEXVERSE_CLI_UPDATE=off :: Windows (export ... on Linux and macOS)
50
+ ```
51
+
52
+ To pin one command — or a whole machine — to a specific source instead:
53
+
54
+ ```console
55
+ dexverse-dl download --manifest manifest.json --out ./dataset --update-index-url https://mirror.internal/simple
56
+ set DEXVERSE_CLI_UPDATE_INDEX_URL=https://mirror.internal/simple
57
+ set DEXVERSE_CLI_UPDATE_EXTRA_INDEX_URL=https://mirror.internal/simple
58
+ set DEXVERSE_CLI_UPDATE_TRUSTED_HOST=mirror.internal
59
+ ```
60
+
61
+ The flag is shorthand for `DEXVERSE_CLI_UPDATE_INDEX_URL`. Naming any of these
62
+ replaces both defaults: pip is asked only there, which is also how to skip the
63
+ PyPI attempt on a network that cannot reach it.
64
+
65
+ `dexverse-dl --update-status` prints the recorded observation as JSON without any
66
+ network access or change: whether the switch is on, how this interpreter was
67
+ installed, and the last upgrade this environment performed, if any. A command
68
+ that changes nothing records nothing, so it leaves no directory behind.
69
+ `dexverse-dl --version` never checks. Pre-releases are ignored unless you set
70
+ `DEXVERSE_CLI_UPDATE_PRERELEASE=1`, and a working copy (`pip install -e .`) is
71
+ never upgraded automatically.
72
+
73
+ ## Download
74
+
75
+ Read a manifest from a file:
76
+
77
+ ```console
78
+ dexverse-dl download --manifest manifest.json --out ./dataset
79
+ ```
80
+
81
+ Or pipe a short-lived manifest directly to stdin:
82
+
83
+ ```console
84
+ type manifest.json | dexverse-dl download --manifest - --out ./dataset
85
+ ```
86
+
87
+ A manifest is either a JSON file list or an object containing `files`. Each
88
+ file requires `download_url` and `file_name`. Optional fields are
89
+ `storage_path`, `file_size`, `sha256`, `hash`, `hash_type`, `meta_data`, and
90
+ `data_protocol`. Unknown top-level keys next to `files` (for example the
91
+ `query_conditions` / `export_info` blocks written by the web batch export) are
92
+ ignored. `data_protocol` is retained in the downloaded metadata sidecar.
93
+ Downloaded metadata is stored in a versioned sidecar and becomes
94
+ `task_instruction.json` when the corresponding HDF5 file is unpacked; only
95
+ top-level `data_protocol` is removed from that algorithm-facing projection.
96
+
97
+ ### Digests
98
+
99
+ A manifest may declare one digest per file. `hash` and `hash_type` are a pair
100
+ and must appear together, so the tool never guesses an algorithm; the legacy
101
+ `sha256` field is the one exception, since its name already carries the type.
102
+ Three types are accepted:
103
+
104
+ | `hash_type` | Meaning | Verification cost |
105
+ | --- | --- | --- |
106
+ | `sha256` | Whole-file SHA-256 | Reads the whole file after download |
107
+ | `md5` | Whole-file MD5, for older records | Reads the whole file after download |
108
+ | `sample_sha256_v1` | Bounded sample over the head, middle, and tail 1 MiB blocks, framed with the file size | Reads at most 3 MiB per file |
109
+
110
+ `sample_sha256_v1` matches the digest the uploader and the web console already
111
+ produce, so their recorded value can be reused directly. It is a deduplication
112
+ fingerprint, not a full checksum: a change outside the sampled windows cannot be
113
+ detected. It does catch truncation and any change inside a sampled window.
114
+ `md5` is fine for detecting transfer corruption but is weak against a deliberate
115
+ collision, so it is not a security boundary.
116
+
117
+ Digests are **not** checked unless you ask for it, because hashing large files
118
+ costs real time:
119
+
120
+ ```console
121
+ dexverse-dl download --manifest manifest.json --out ./dataset --verify
122
+ ```
123
+
124
+ Without `--verify`, only `file_size` is enforced (when the manifest declares
125
+ it), and the download state records no digest — so a later `--verify` run still
126
+ rechecks the file instead of trusting an unchecked claim. With `--verify`, a
127
+ mismatch removes the partial and fails that file.
128
+
129
+ The download command renders one progress display per batch: a batch line
130
+ (percent, file counter, transferred bytes, rate, remaining time, elapsed time)
131
+ and a line naming the current file. A retry prints a single persistent warning
132
+ above the display and updates the same progress line, so no second progress bar
133
+ is ever added:
134
+
135
+ ```console
136
+ Download ━━━━━━━━━━━━━━─────────────────────── 38% 13/33 88.0 GB/228.3 GB 19.4 MB/s 1:15:47 < 2:00:49
137
+ downloading: piper_v02_0920_raw/episode_000007.hdf5
138
+ warning: retrying piper_v02_0920_raw/episode_000012.hdf5 (attempt 2/5) in 2.0s: transient HTTP status 503
139
+ Download ━━━━━━━━━━━━━━──────────────────────── 38% 13/33 1 failed 88.0 GB/228.3 GB 19.4 MB/s 1:15:47 < 2:00:49
140
+ retrying: piper_v02_0920_raw/episode_000012.hdf5 (retry 2/5) - transient HTTP status 503; next attempt in 2.0s
141
+ ```
142
+
143
+ The bar is a filled rail drawn with rich's own glyphs: `━` for a filled cell and
144
+ `─` for the rest. There is no frame around it, and no mark drawn inside the empty
145
+ part either: unlike a space-filled bar the rail delimits itself, which is why
146
+ `pip`, `rich`, and `tqdm` leave the brackets out. rich builds its rail from one
147
+ glyph and shows the split with colour, which is why rich can afford half cells;
148
+ here the two weights carry the split, and that rules half cells out. A half cell of
149
+ fill is heavy on one side of its cell and blank on the other, while a light rail
150
+ can only start at the next boundary, so the junction gains and loses half a cell
151
+ as the fill crosses each one — a notch that blinks. The fill therefore ends on
152
+ cell boundaries only and meets the rail directly, so every frame has one junction
153
+ shape and only its position moves; the percentage and byte counters carry the finer
154
+ resolution. Holding a permanent half-cell gap open at the junction was tried as
155
+ well, and reads worse than meeting flush, so there is none. The fill is drawn in
156
+ the line's own colour and only the rest of the rail steps back to a dim shade, so
157
+ the bar never competes with the numbers. Because the empty part differs by glyph
158
+ and not by colour alone, progress stays readable in a piped log and under
159
+ `NO_COLOR`. Counters and byte totals carry no words — `13/33` and
160
+ `88.0 GB/228.3 GB` are self-describing, as they are in every common progress
161
+ line — and the two clocks are paired the way `tqdm` pairs them, elapsed on the
162
+ left of `<` and remaining on the right, so neither needs a label and neither can
163
+ be mistaken for the other. The right side stays `--:--` while the total is
164
+ incomplete, since only a complete total yields an estimate.
165
+
166
+ The rail keeps one length for the whole batch: the first frame fixes it, so a
167
+ field that appears or grows mid-run (a failure count, a byte total crossing into
168
+ the next unit, the elapsed clock) cannot take cells from it. A shorter bar would
169
+ re-render a shorter fill and read as progress going backwards. That first frame
170
+ also holds room for the failure count before anything has failed, so the first
171
+ failure cannot push the remaining time off the end of the line. Only a terminal
172
+ resize measures the rail again, and on a narrow terminal the tail is clipped
173
+ before the rail is. The rail is up to 40 cells — long enough that the first one
174
+ fills at about 2.5%, since a bar that only starts moving at 4% is doing less to
175
+ calm the wait than it could — and it takes whatever fewer columns a narrow
176
+ terminal leaves over.
177
+
178
+ The live region is rebuilt on rich's refresh tick as well as whenever new state
179
+ arrives, so the elapsed clock keeps counting while one file is being worked on.
180
+ Without that tick the clock would freeze between callbacks and then jump by the
181
+ length of the file, which reads as a stall.
182
+
183
+ Percent, byte counters, and the bar follow transferred bytes; the file counter
184
+ advances when a file is published or skipped. When the manifest omits
185
+ `file_size` for any entry, the total is shown as a lower bound (`~6.3 GB`) that
186
+ grows as each transfer reports its real size, and the bar stays an empty rail
187
+ rather than filling against a bound that is still growing; percent and remaining
188
+ time are withheld until the total is complete instead of being guessed from a
189
+ partial sum. Pass `--probe-sizes` to read those sizes from the server first (one
190
+ small ranged request per entry that lacks one), so the total, percentage, and
191
+ remaining time are correct before the first byte moves:
192
+
193
+ ```console
194
+ dexverse-dl download --manifest manifest.json --out ./dataset --probe-sizes
195
+ ```
196
+
197
+ The probe is off by default because a complete manifest needs no extra requests.
198
+ It costs one `GET` with `Range: bytes=0-0` per unknown entry and tolerates a
199
+ server that ignores the range. Piped or logged output (no terminal) prints the
200
+ same line periodically instead of repainting in place, so redirecting to a file
201
+ stays readable.
202
+
203
+ Pass `--no-progress` to suppress the live display, the per-file line, and retry
204
+ warnings while keeping the final summary:
205
+
206
+ ```console
207
+ dexverse-dl download --manifest manifest.json --out ./dataset --no-progress
208
+ ```
209
+
210
+
211
+ Downloads use bounded parallelism, retries, and compatible partial-file resume.
212
+ Within a manifest, the first occurrence of a target path keeps its name; later
213
+ occurrences receive `-1`, `-2`, etc. before the final extension (for example,
214
+ `episode1.hdf5`, `episode1-1.hdf5`, `episode1-2.hdf5`). Numbering skips names and
215
+ paths already claimed by other entries, including partial files and metadata.
216
+ Different relative directories remain independent. Names are assigned in manifest
217
+ order, with Windows case-insensitive matching, before any downloads start.
218
+ Rerunning the same manifest preserves the assigned names for skipping and resume;
219
+ existing files on disk do not trigger new suffixes. This is not cross-batch name
220
+ allocation: changing the manifest entries or their order can change the mapping.
221
+ When a digest is declared it is checked only under `--verify`; without it a file
222
+ is accepted on its declared `file_size` alone. TLS certificate verification is
223
+ disabled by default for internal endpoints; pass `--secure` to enable
224
+ certificate verification.
225
+
226
+ ## Unpack
227
+
228
+ Unpack one HDF5 file to the restored raw dataset layout:
229
+
230
+ ```console
231
+ dexverse-dl unpack --input ./packed/session.hdf5 --output ./restored
232
+ ```
233
+
234
+ Or unpack every `*.hdf5` under a directory, preserving relative paths:
235
+
236
+ ```console
237
+ dexverse-dl unpack --input-dir ./packed --output ./restored
238
+ ```
239
+
240
+ Unpacking validates the complete versioned schema before transactionally
241
+ publishing a restored session, and retains the supported unversioned HDF5 read
242
+ path. It accepts `--progress/--no-progress`; the batch line reports the same
243
+ filled rail as the download command, driven by restored sessions, plus the
244
+ session count, the restored frame count, and the elapsed time:
245
+
246
+ ```console
247
+ Unpacking ━━━━━━━━━━━━━─────────────────────────── 33% 3/9 1200 frames restored 00:42
248
+ packed/episode_000003.hdf5 -> restored/episode_000003
249
+ ```
250
+
251
+ The rail is empty until the first callback reports how many sessions the input
252
+ holds, and shows `?%` until then. That batch has no rate, so it has no estimate
253
+ and only one clock, printed bare because there is nothing to tell it apart from.
254
+ The frame count is the running total over the whole batch, not the count for the
255
+ session named on the line below it, which is why it is spelled out as `frames
256
+ restored`: it only ever counts sessions that finished, so a failed session adds
257
+ nothing, and it reaches its final value in the summary line. It is always printed,
258
+ even at zero, so the line keeps one shape from the first frame to the last.
259
+ Batches continue after an item failure but exit with status 1 if any item fails.
260
+
261
+ ## Convert
262
+
263
+ `convert` is kept as a compatibility alias for `unpack`, because pipelines that
264
+ were written against the earlier releases still call it. It takes the same
265
+ options and performs the same restore:
266
+
267
+ ```console
268
+ dexverse-dl convert --input ./packed/session.hdf5 --output ./restored
269
+ dexverse-dl convert --input-dir ./packed --output ./restored
270
+ ```
271
+
272
+ Its only differences are the labels on the progress line and in the summary:
273
+ `Converting` and `converted=` instead of `Unpacking` and `unpacked=`. New
274
+ scripts should prefer `unpack`.
275
+
276
+ ## Code layout
277
+
278
+ `cli.py` only wires typer to the chains; everything else lives in the package for
279
+ the chain it belongs to, and the two chains never import each other.
280
+
281
+ ```
282
+ dexverse_cli/
283
+ cli.py download / unpack / convert, and nothing else
284
+ common/ what both chains share: the .dexverse state directory, digests
285
+ progress/ what a running command shows: views, the rail, the reporters
286
+ view.py the frozen snapshots a batch hands to its display
287
+ bar.py the rail's glyphs, geometry, and styling
288
+ formatting.py byte counts and clocks as the lines show them
289
+ reporter.py the live two-line region and the reports that drive it
290
+ download/ manifest -> plan -> one file -> batch -> runner
291
+ restore/ session format -> raw layout -> publish -> batch -> runner
292
+ ```
293
+
294
+ Each chain follows the same shape, so a reader who knows one knows the other: an
295
+ input contract (`download/manifest.py`, `restore/schema.py`), a plan
296
+ (`download/filesystem.py`, `restore/versioned.py`), the work for one item
297
+ (`download/transfer.py` `FileTransfer`, `restore/session.py` `SessionRestore`), the
298
+ one owner of the batch's state and counters (`*/batch.py`), and the orchestration
299
+ that decides the order (`*/runner.py`).
300
+
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dexverse-cli"
7
- version = "0.1.3.post2"
8
- description = "Download, pack, and unpack DexVerse datasets"
7
+ dynamic = ["version"]
8
+ description = "Download DexVerse dataset manifests and unpack HDF5 sessions"
9
9
  readme = "src/dexverse_cli/README.md"
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
@@ -13,16 +13,12 @@ dependencies = [
13
13
  "h5py>=3.11,<4",
14
14
  "httpx>=0.28,<1",
15
15
  "numpy>=1.26,<3",
16
+ "packaging>=24,<27",
16
17
  "pillow>=10,<13",
17
18
  "rich>=13.9,<15",
18
19
  "typer>=0.16,<1",
19
20
  ]
20
21
 
21
- [project.optional-dependencies]
22
- jetson = [
23
- "PyGObject>=3.42,<4; platform_system == 'Linux'",
24
- ]
25
-
26
22
  [project.scripts]
27
23
  dexverse-dl = "dexverse_cli.cli:app"
28
24
 
@@ -33,6 +29,11 @@ dev = [
33
29
  "ruff>=0.11,<1",
34
30
  ]
35
31
 
32
+ [tool.hatch.version]
33
+ # One version, declared once: the build backend reads the package marker, so a
34
+ # release bump cannot leave the wheel metadata disagreeing with the code.
35
+ path = "src/dexverse_cli/__init__.py"
36
+
36
37
  [tool.hatch.build.targets.wheel]
37
38
  packages = ["src/dexverse_cli"]
38
39
 
@@ -0,0 +1,285 @@
1
+ # dexverse-cli
2
+
3
+ dexverse-cli downloads DexVerse dataset manifests over HTTP(S) and unpacks
4
+ HDF5 sessions back to the restored dataset layout. It requires
5
+ Python 3.10 or newer.
6
+
7
+
8
+ ## Install
9
+
10
+ ```console
11
+ pip install dexverse-cli
12
+ ```
13
+
14
+ ## Automatic updates
15
+
16
+ Every business command (`download`, `unpack`, `convert`) first asks whether a
17
+ newer `dexverse-cli` is published. Public PyPI is asked first, because that is
18
+ where a new release appears immediately; when PyPI cannot be reached the check
19
+ falls back to the source this machine's pip is configured with (`pip.conf`,
20
+ `PIP_INDEX_URL`, mirrors, proxies). The check is bounded to about two seconds.
21
+
22
+ - Nothing new, or nothing to ask: the command runs as usual.
23
+ - A newer release exists: the CLI upgrades itself in place with
24
+ `python -m pip install --upgrade dexverse-cli` from the source that answered,
25
+ and then exits with status **20**. Run the same command again; the rerun uses
26
+ the new version.
27
+ - The check times out, pip is missing, no source answers, or the upgrade fails:
28
+ one line is printed and the requested command still runs.
29
+
30
+ The switch is on by default. Turn the check off for one command, or permanently:
31
+
32
+ ```console
33
+ dexverse-dl download --manifest manifest.json --out ./dataset --no-update-check
34
+ set DEXVERSE_CLI_UPDATE=off :: Windows (export ... on Linux and macOS)
35
+ ```
36
+
37
+ To pin one command — or a whole machine — to a specific source instead:
38
+
39
+ ```console
40
+ dexverse-dl download --manifest manifest.json --out ./dataset --update-index-url https://mirror.internal/simple
41
+ set DEXVERSE_CLI_UPDATE_INDEX_URL=https://mirror.internal/simple
42
+ set DEXVERSE_CLI_UPDATE_EXTRA_INDEX_URL=https://mirror.internal/simple
43
+ set DEXVERSE_CLI_UPDATE_TRUSTED_HOST=mirror.internal
44
+ ```
45
+
46
+ The flag is shorthand for `DEXVERSE_CLI_UPDATE_INDEX_URL`. Naming any of these
47
+ replaces both defaults: pip is asked only there, which is also how to skip the
48
+ PyPI attempt on a network that cannot reach it.
49
+
50
+ `dexverse-dl --update-status` prints the recorded observation as JSON without any
51
+ network access or change: whether the switch is on, how this interpreter was
52
+ installed, and the last upgrade this environment performed, if any. A command
53
+ that changes nothing records nothing, so it leaves no directory behind.
54
+ `dexverse-dl --version` never checks. Pre-releases are ignored unless you set
55
+ `DEXVERSE_CLI_UPDATE_PRERELEASE=1`, and a working copy (`pip install -e .`) is
56
+ never upgraded automatically.
57
+
58
+ ## Download
59
+
60
+ Read a manifest from a file:
61
+
62
+ ```console
63
+ dexverse-dl download --manifest manifest.json --out ./dataset
64
+ ```
65
+
66
+ Or pipe a short-lived manifest directly to stdin:
67
+
68
+ ```console
69
+ type manifest.json | dexverse-dl download --manifest - --out ./dataset
70
+ ```
71
+
72
+ A manifest is either a JSON file list or an object containing `files`. Each
73
+ file requires `download_url` and `file_name`. Optional fields are
74
+ `storage_path`, `file_size`, `sha256`, `hash`, `hash_type`, `meta_data`, and
75
+ `data_protocol`. Unknown top-level keys next to `files` (for example the
76
+ `query_conditions` / `export_info` blocks written by the web batch export) are
77
+ ignored. `data_protocol` is retained in the downloaded metadata sidecar.
78
+ Downloaded metadata is stored in a versioned sidecar and becomes
79
+ `task_instruction.json` when the corresponding HDF5 file is unpacked; only
80
+ top-level `data_protocol` is removed from that algorithm-facing projection.
81
+
82
+ ### Digests
83
+
84
+ A manifest may declare one digest per file. `hash` and `hash_type` are a pair
85
+ and must appear together, so the tool never guesses an algorithm; the legacy
86
+ `sha256` field is the one exception, since its name already carries the type.
87
+ Three types are accepted:
88
+
89
+ | `hash_type` | Meaning | Verification cost |
90
+ | --- | --- | --- |
91
+ | `sha256` | Whole-file SHA-256 | Reads the whole file after download |
92
+ | `md5` | Whole-file MD5, for older records | Reads the whole file after download |
93
+ | `sample_sha256_v1` | Bounded sample over the head, middle, and tail 1 MiB blocks, framed with the file size | Reads at most 3 MiB per file |
94
+
95
+ `sample_sha256_v1` matches the digest the uploader and the web console already
96
+ produce, so their recorded value can be reused directly. It is a deduplication
97
+ fingerprint, not a full checksum: a change outside the sampled windows cannot be
98
+ detected. It does catch truncation and any change inside a sampled window.
99
+ `md5` is fine for detecting transfer corruption but is weak against a deliberate
100
+ collision, so it is not a security boundary.
101
+
102
+ Digests are **not** checked unless you ask for it, because hashing large files
103
+ costs real time:
104
+
105
+ ```console
106
+ dexverse-dl download --manifest manifest.json --out ./dataset --verify
107
+ ```
108
+
109
+ Without `--verify`, only `file_size` is enforced (when the manifest declares
110
+ it), and the download state records no digest — so a later `--verify` run still
111
+ rechecks the file instead of trusting an unchecked claim. With `--verify`, a
112
+ mismatch removes the partial and fails that file.
113
+
114
+ The download command renders one progress display per batch: a batch line
115
+ (percent, file counter, transferred bytes, rate, remaining time, elapsed time)
116
+ and a line naming the current file. A retry prints a single persistent warning
117
+ above the display and updates the same progress line, so no second progress bar
118
+ is ever added:
119
+
120
+ ```console
121
+ Download ━━━━━━━━━━━━━━─────────────────────── 38% 13/33 88.0 GB/228.3 GB 19.4 MB/s 1:15:47 < 2:00:49
122
+ downloading: piper_v02_0920_raw/episode_000007.hdf5
123
+ warning: retrying piper_v02_0920_raw/episode_000012.hdf5 (attempt 2/5) in 2.0s: transient HTTP status 503
124
+ Download ━━━━━━━━━━━━━━──────────────────────── 38% 13/33 1 failed 88.0 GB/228.3 GB 19.4 MB/s 1:15:47 < 2:00:49
125
+ retrying: piper_v02_0920_raw/episode_000012.hdf5 (retry 2/5) - transient HTTP status 503; next attempt in 2.0s
126
+ ```
127
+
128
+ The bar is a filled rail drawn with rich's own glyphs: `━` for a filled cell and
129
+ `─` for the rest. There is no frame around it, and no mark drawn inside the empty
130
+ part either: unlike a space-filled bar the rail delimits itself, which is why
131
+ `pip`, `rich`, and `tqdm` leave the brackets out. rich builds its rail from one
132
+ glyph and shows the split with colour, which is why rich can afford half cells;
133
+ here the two weights carry the split, and that rules half cells out. A half cell of
134
+ fill is heavy on one side of its cell and blank on the other, while a light rail
135
+ can only start at the next boundary, so the junction gains and loses half a cell
136
+ as the fill crosses each one — a notch that blinks. The fill therefore ends on
137
+ cell boundaries only and meets the rail directly, so every frame has one junction
138
+ shape and only its position moves; the percentage and byte counters carry the finer
139
+ resolution. Holding a permanent half-cell gap open at the junction was tried as
140
+ well, and reads worse than meeting flush, so there is none. The fill is drawn in
141
+ the line's own colour and only the rest of the rail steps back to a dim shade, so
142
+ the bar never competes with the numbers. Because the empty part differs by glyph
143
+ and not by colour alone, progress stays readable in a piped log and under
144
+ `NO_COLOR`. Counters and byte totals carry no words — `13/33` and
145
+ `88.0 GB/228.3 GB` are self-describing, as they are in every common progress
146
+ line — and the two clocks are paired the way `tqdm` pairs them, elapsed on the
147
+ left of `<` and remaining on the right, so neither needs a label and neither can
148
+ be mistaken for the other. The right side stays `--:--` while the total is
149
+ incomplete, since only a complete total yields an estimate.
150
+
151
+ The rail keeps one length for the whole batch: the first frame fixes it, so a
152
+ field that appears or grows mid-run (a failure count, a byte total crossing into
153
+ the next unit, the elapsed clock) cannot take cells from it. A shorter bar would
154
+ re-render a shorter fill and read as progress going backwards. That first frame
155
+ also holds room for the failure count before anything has failed, so the first
156
+ failure cannot push the remaining time off the end of the line. Only a terminal
157
+ resize measures the rail again, and on a narrow terminal the tail is clipped
158
+ before the rail is. The rail is up to 40 cells — long enough that the first one
159
+ fills at about 2.5%, since a bar that only starts moving at 4% is doing less to
160
+ calm the wait than it could — and it takes whatever fewer columns a narrow
161
+ terminal leaves over.
162
+
163
+ The live region is rebuilt on rich's refresh tick as well as whenever new state
164
+ arrives, so the elapsed clock keeps counting while one file is being worked on.
165
+ Without that tick the clock would freeze between callbacks and then jump by the
166
+ length of the file, which reads as a stall.
167
+
168
+ Percent, byte counters, and the bar follow transferred bytes; the file counter
169
+ advances when a file is published or skipped. When the manifest omits
170
+ `file_size` for any entry, the total is shown as a lower bound (`~6.3 GB`) that
171
+ grows as each transfer reports its real size, and the bar stays an empty rail
172
+ rather than filling against a bound that is still growing; percent and remaining
173
+ time are withheld until the total is complete instead of being guessed from a
174
+ partial sum. Pass `--probe-sizes` to read those sizes from the server first (one
175
+ small ranged request per entry that lacks one), so the total, percentage, and
176
+ remaining time are correct before the first byte moves:
177
+
178
+ ```console
179
+ dexverse-dl download --manifest manifest.json --out ./dataset --probe-sizes
180
+ ```
181
+
182
+ The probe is off by default because a complete manifest needs no extra requests.
183
+ It costs one `GET` with `Range: bytes=0-0` per unknown entry and tolerates a
184
+ server that ignores the range. Piped or logged output (no terminal) prints the
185
+ same line periodically instead of repainting in place, so redirecting to a file
186
+ stays readable.
187
+
188
+ Pass `--no-progress` to suppress the live display, the per-file line, and retry
189
+ warnings while keeping the final summary:
190
+
191
+ ```console
192
+ dexverse-dl download --manifest manifest.json --out ./dataset --no-progress
193
+ ```
194
+
195
+
196
+ Downloads use bounded parallelism, retries, and compatible partial-file resume.
197
+ Within a manifest, the first occurrence of a target path keeps its name; later
198
+ occurrences receive `-1`, `-2`, etc. before the final extension (for example,
199
+ `episode1.hdf5`, `episode1-1.hdf5`, `episode1-2.hdf5`). Numbering skips names and
200
+ paths already claimed by other entries, including partial files and metadata.
201
+ Different relative directories remain independent. Names are assigned in manifest
202
+ order, with Windows case-insensitive matching, before any downloads start.
203
+ Rerunning the same manifest preserves the assigned names for skipping and resume;
204
+ existing files on disk do not trigger new suffixes. This is not cross-batch name
205
+ allocation: changing the manifest entries or their order can change the mapping.
206
+ When a digest is declared it is checked only under `--verify`; without it a file
207
+ is accepted on its declared `file_size` alone. TLS certificate verification is
208
+ disabled by default for internal endpoints; pass `--secure` to enable
209
+ certificate verification.
210
+
211
+ ## Unpack
212
+
213
+ Unpack one HDF5 file to the restored raw dataset layout:
214
+
215
+ ```console
216
+ dexverse-dl unpack --input ./packed/session.hdf5 --output ./restored
217
+ ```
218
+
219
+ Or unpack every `*.hdf5` under a directory, preserving relative paths:
220
+
221
+ ```console
222
+ dexverse-dl unpack --input-dir ./packed --output ./restored
223
+ ```
224
+
225
+ Unpacking validates the complete versioned schema before transactionally
226
+ publishing a restored session, and retains the supported unversioned HDF5 read
227
+ path. It accepts `--progress/--no-progress`; the batch line reports the same
228
+ filled rail as the download command, driven by restored sessions, plus the
229
+ session count, the restored frame count, and the elapsed time:
230
+
231
+ ```console
232
+ Unpacking ━━━━━━━━━━━━━─────────────────────────── 33% 3/9 1200 frames restored 00:42
233
+ packed/episode_000003.hdf5 -> restored/episode_000003
234
+ ```
235
+
236
+ The rail is empty until the first callback reports how many sessions the input
237
+ holds, and shows `?%` until then. That batch has no rate, so it has no estimate
238
+ and only one clock, printed bare because there is nothing to tell it apart from.
239
+ The frame count is the running total over the whole batch, not the count for the
240
+ session named on the line below it, which is why it is spelled out as `frames
241
+ restored`: it only ever counts sessions that finished, so a failed session adds
242
+ nothing, and it reaches its final value in the summary line. It is always printed,
243
+ even at zero, so the line keeps one shape from the first frame to the last.
244
+ Batches continue after an item failure but exit with status 1 if any item fails.
245
+
246
+ ## Convert
247
+
248
+ `convert` is kept as a compatibility alias for `unpack`, because pipelines that
249
+ were written against the earlier releases still call it. It takes the same
250
+ options and performs the same restore:
251
+
252
+ ```console
253
+ dexverse-dl convert --input ./packed/session.hdf5 --output ./restored
254
+ dexverse-dl convert --input-dir ./packed --output ./restored
255
+ ```
256
+
257
+ Its only differences are the labels on the progress line and in the summary:
258
+ `Converting` and `converted=` instead of `Unpacking` and `unpacked=`. New
259
+ scripts should prefer `unpack`.
260
+
261
+ ## Code layout
262
+
263
+ `cli.py` only wires typer to the chains; everything else lives in the package for
264
+ the chain it belongs to, and the two chains never import each other.
265
+
266
+ ```
267
+ dexverse_cli/
268
+ cli.py download / unpack / convert, and nothing else
269
+ common/ what both chains share: the .dexverse state directory, digests
270
+ progress/ what a running command shows: views, the rail, the reporters
271
+ view.py the frozen snapshots a batch hands to its display
272
+ bar.py the rail's glyphs, geometry, and styling
273
+ formatting.py byte counts and clocks as the lines show them
274
+ reporter.py the live two-line region and the reports that drive it
275
+ download/ manifest -> plan -> one file -> batch -> runner
276
+ restore/ session format -> raw layout -> publish -> batch -> runner
277
+ ```
278
+
279
+ Each chain follows the same shape, so a reader who knows one knows the other: an
280
+ input contract (`download/manifest.py`, `restore/schema.py`), a plan
281
+ (`download/filesystem.py`, `restore/versioned.py`), the work for one item
282
+ (`download/transfer.py` `FileTransfer`, `restore/session.py` `SessionRestore`), the
283
+ one owner of the batch's state and counters (`*/batch.py`), and the orchestration
284
+ that decides the order (`*/runner.py`).
285
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4rc2"