Metadata-Version: 2.5
Name: eastworlds-forge
Version: 0.5.2
Summary: The Forge command-line interface: sign in with a Forge API key, download and upload dataset episodes, convert MCAP <-> LeRobot v2.1 locally.
Project-URL: Documentation, https://forge.eastworlds.io/how-to/cli
Project-URL: Source, https://github.com/Eastworld-Labs/forge_cli
Project-URL: Releases, https://github.com/Eastworld-Labs/forge_cli/releases
Requires-Python: >=3.10
Requires-Dist: av>=15
Requires-Dist: httpx>=0.27
Requires-Dist: mcap-ros2-support>=0.5
Requires-Dist: mcap>=1.2
Requires-Dist: numpy>=1.26
Requires-Dist: platformdirs>=4
Requires-Dist: pyarrow>=15
Requires-Dist: questionary>=2
Requires-Dist: rich>=13
Requires-Dist: tomli-w>=1
Requires-Dist: tomli>=2; python_version < '3.11'
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: keyring
Requires-Dist: keyring>=25; extra == 'keyring'
Description-Content-Type: text/markdown

# forge

The Forge command-line interface. Sign in once with a Forge API key, then download and upload dataset episodes, convert MCAP ↔ LeRobot v2.1, and lower the video frame rate. Conversion and frame-rate changes run on your computer; for LeRobot v2.1, `forge download --server-export` can also have Forge build the dataset. Forge stores and serves episodes exactly as they were uploaded.

The full guide is on Forge: **[Documentation → Command-Line Interface (CLI)](https://forge.eastworlds.io/how-to/cli)**.

## Install

**Linux only:** x86_64 or arm64, glibc 2.28+ (e.g. Ubuntu 20.04+, Debian 12+, RHEL/Rocky 8+). On Windows, run it inside WSL2 (Ubuntu). macOS is not supported. No root, Python or ffmpeg needed.

```bash
curl -fsSL https://forge.eastworlds.io/install.sh | bash
```

This installs forge with its own Python into `~/.forge` (about 310 MB) and links `forge` and `eforge` (the same program) into `~/.local/bin`. A specific version: `… | bash -s -- 0.2.0`.

Prefer a Python tool manager (Linux)? `uv tool install eastworlds-forge` or `pipx install eastworlds-forge`.

## Sign in

```bash
forge
```

The first run opens **Forge → API keys** with a CLI key filled in. Press **Create**, copy the command the page shows, and paste it at the prompt. In CI, set `FORGE_API_KEY` instead.

## Use

```bash
forge datasets list                  # add --shared for datasets shared with you, -n ORG for one organisation's
forge download eastworlds/yam-fold-handkerchief -o ./data --verdict valid
forge download eastworlds/yam-fold-handkerchief -o ./lerobot --format lerobot-v21 --fps 10
forge upload ./recordings/2026-09-29 --to eastworlds/yam-fold-handkerchief
forge convert mcap-to-lerobot ./data/eastworlds/yam-fold-handkerchief -o ./lerobot/yam_fold
forge convert lerobot-to-mcap ./lerobot/yam_fold -o ./mcap_again
```

Global options such as `--json` and `-y` go before the command: `forge --json datasets list`.

`forge update` updates, `forge doctor` checks the setup, `forge uninstall` removes everything (your keys stay unless you confirm).

| Exit code | Meaning |
|---|---|
| 0 | OK |
| 1 | Failure |
| 2 | Usage or validation error |
| 3 | Bad or expired key |
| 4 | Missing scope |
| 5 | Not found |
| 6 | Conflict |
| 7 | Server or network error |
| 130 | Interrupted |

## Annotations

Add your own annotations (workflow runs with lanes of segments) to a dataset's existing episodes. The file is `forge.annotations.v1` JSONL, one episode per line: `{"episode": "<name>" and/or "episode_index": N, "workflows": {"<name>": {"results": [{"segments": [...]}], "finishedAt": "...", "failed": false}}}`. Everyone who can view the dataset sees uploaded annotations, and the original episodes and annotations are not changed. Annotations cannot be deleted from the CLI; dataset owners can hide a run in Episode view settings. Uploaded annotations are not part of downloads or exports. `upload` also takes an MCAP annotations file (a real MCAP file whose name ends in `.mcap`); Forge checks it itself, so use `upload --dry-run` rather than `check` (JSONL only) for it. `forge download --annotation-format mcap` (with `--content full` or `annotations`) packs `forge/annotations.mcap` instead of `forge/annotations.jsonl`. Your API key needs `datasets:write` for `check` and `upload` and `datasets:read` for `show`. Limits: 20 MiB, 5,000 lines and 1 MiB per line (the first two are checked locally before anything is sent; a file over either exits 2). Forge limits how fast annotations can be uploaded; the CLI waits and retries automatically. Prefer questions to flags? Run `forge` and choose **Annotations**: Add annotations to existing episodes (a check, the plan and one question), See what's on an episode, or Get a sample file; it shows the command it ran.

Rules for the file: one JSON object per line, with no duplicate keys. `schema`, if present, is `forge.annotations.v1`. Name the episode with `episode` and/or `episode_index` (if both, they must be the same episode). `workflows` holds up to 20 workflows, each name 1 to 200 characters. Each workflow has a `results` list (up to 50), and a result may carry a `segments` list. Unless `failed` is true, a workflow needs at least one segment; one episode line has at most 2,000 segments. `startMs` and `endMs` are whole milliseconds with 0 <= `startMs` < `endMs` <= the episode's length (up to 24 hours when the length is unknown). Every segment needs a non-blank `title` (at most 200 characters); `description` (at most 2,000 characters), `qualityScore` (a whole number from 1 to 5) and `reviewStatus` (`pending`, `accepted` or `rejected`) are optional; other fields are ignored. Segments inside one result must not overlap (touching is fine). `finishedAt` is optional (ISO-8601, at most a day in the future; default: the upload time). A line that breaks a rule is rejected; `check` and `upload` print each reason as `line N: <json path>: <reason>` (the first 10 lines, or all with `--all`). Lines already added are reported as "N already added, will be skipped" and are not an error.

```bash
forge annotations sample > a.jsonl          # a two-line file that follows the format
# edit the episode names (or episode_index) to match your dataset
forge annotations check eastworlds/yam-fold-handkerchief a.jsonl
forge annotations upload eastworlds/yam-fold-handkerchief a.jsonl     # shows the plan, asks, uploads
forge annotations upload eastworlds/yam-fold-handkerchief a.jsonl --yes   # no question (scripts)
forge annotations show eastworlds/yam-fold-handkerchief EPISODE
```

`upload` runs the check, prints what will change (`Add annotations to N episodes in NAMESPACE/DATASET:`, the first 8 episodes with their workflow names and segment counts, `and M more`, how many lines are already added and will be skipped, and with `--partial` how many lines with problems will be skipped), prints the notes, and then asks `Upload these annotations?` before sending anything. The global `--yes` skips the question; without a terminal (or with `--json`) it is required, otherwise `upload` exits 2 with "pass --yes to upload without a prompt". Answering no exits 130. `--dry-run` prints the plan and stops without asking. When nothing can be added it says so and exits 0 without asking. Lines with problems stop the upload (exit 1) unless you pass `--partial`. `Added annotations to N episodes.` is printed on success, and `Nothing to add: these annotations are already on those episodes.` when every line was already added. Exit codes: 0 ok (also when everything was already added), 1 some lines would be skipped (check, `upload --dry-run`) or have problems without `--partial`, or no line matches, 2 usage or local file problems (not a file, over the size limits, no lines) or `upload` without `--yes` and without a terminal, 3 bad key, 4 missing permission (`datasets:write` for `check` and `upload`, `datasets:read` for `show`), 5 dataset or episode not found, 7 server or network error, 130 you answered no to the question. All commands are in [COMMANDS.md](COMMANDS.md).

## Interactive mode and tab completion

In a terminal, forge can ask instead of making you remember flags. Type `forge` on its own for a menu that reaches every command (and, under "Advanced options", the useful flags), or leave arguments out of `forge download`, `forge upload`, `forge episodes show`, `forge uploads status` or `forge downloads show` and it asks for them. Every guided flow ends by printing the equivalent `forge …` command, so you can copy it into a script.

Menus appear only when both stdin and stderr are terminals, `CI` is not set, and you did not pass `--yes` or `--json`. Anywhere else a missing argument is the usual usage error, and nothing is ever written to stdout by a menu.

Tab completion for datasets, tasks, tags, file types, annotation runs (it uses your saved key, waits at most 2 seconds, and stays silent if it cannot reach Forge):

```bash
forge --install-completion    # then open a new shell
forge --show-completion       # print the script instead of installing it
```

## Develop

```bash
python3.11 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest
.venv/bin/ruff check src tests
```

- `src/forge_cli/convert/` is a port of the production-verified MCAP → LeRobot v2.1 converter. `tests/test_convert_golden.py` compares the two on real recordings when `FORGE_VERIFIED_SAMPLE` and `FORGE_GOLDEN_EPISODES` are set.
- `install/install.sh` is the installer; `tests/test_install.py` runs it against a local fake release.
- `scripts/build_release.py --out dist/release` builds what the download host serves. On a `vX.Y.Z` tag, `.github/workflows/release.yml` publishes it to Tencent COS behind EdgeOne (`https://cli-forge.eastworlds.io`); setup in [docs/RELEASE_INFRA.md](docs/RELEASE_INFRA.md).

Design: [docs/INITIAL_PLAN_V2.md](docs/INITIAL_PLAN_V2.md). What was built: [docs/INITIAL_DONE_V2.md](docs/INITIAL_DONE_V2.md).
