Metadata-Version: 2.4
Name: nuuduu
Version: 0.4.1
Summary: Python SDK and CLI for Nuuduu Atlas — sync robotics training datasets (LeRobot, MCAP, HEVC) to your local machine.
Keywords: robotics,lerobot,pytorch,dataset,atlas,mcap,imitation-learning,physical-ai
Author: Nuuduu UAB
Author-email: Nuuduu UAB <info@nuuduu.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Classifier: Environment :: Console
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Requires-Dist: pydantic-settings>=2.0
Requires-Dist: nuuduu[cli] ; extra == 'all'
Requires-Dist: typer>=0.12 ; extra == 'cli'
Requires-Dist: rich>=13 ; extra == 'cli'
Requires-Dist: pytest>=8 ; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30 ; extra == 'dev'
Requires-Dist: typer>=0.12 ; extra == 'dev'
Requires-Dist: rich>=13 ; extra == 'dev'
Requires-Dist: build ; extra == 'dev'
Requires-Dist: twine ; extra == 'dev'
Requires-Python: >=3.10
Project-URL: Homepage, https://nuuduu.ai/atlas
Project-URL: Documentation, https://pypi.org/project/nuuduu/
Project-URL: Repository, https://bitbucket.org/nuuduu/nuuduu
Project-URL: Issues, https://bitbucket.org/nuuduu/nuuduu/issues
Project-URL: Changelog, https://bitbucket.org/nuuduu/nuuduu/src/main/CHANGELOG.md
Provides-Extra: all
Provides-Extra: cli
Provides-Extra: dev
Description-Content-Type: text/markdown

# Nuuduu

[![PyPI version](https://badge.fury.io/py/nuuduu.svg)](https://pypi.org/project/nuuduu/)
[![Python versions](https://img.shields.io/pypi/pyversions/nuuduu.svg)](https://pypi.org/project/nuuduu/)
[![Build status](https://img.shields.io/bitbucket/pipelines/nuuduu/nuuduu/main?style=flat-square&logo=bitbucket)](https://bitbucket.org/nuuduu/nuuduu/pipelines)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**Nuuduu** is a Python SDK and CLI that gets robotics training data from the [Nuuduu Atlas](https://nuuduu.ai/atlas) library onto your machine as a **PyTorch-ready dataset** — and automatically requests new data collection when the library does not have enough.

| Step | What it does |
|---|---|
| **Search** | Find episodes in the Atlas library by task description and country |
| **Bundle** | Package matching episodes into a training dataset (LeRobot by default) |
| **Sync** | Download ready bundles to your local dataset directory |
| **Request** | Order new episodes to be collected when the library is short |

Use what Atlas already has, or combine `--min-episodes` on bundle to **bundle available episodes and request the rest in one command**.

| | URL |
|---|---|
| Atlas web app | https://nuuduu.ai/atlas |
| Atlas API (default) | https://nuuduu.com |

The CLI talks to the API at `nuuduu.com`. Browse episodes in the [Atlas web app](https://nuuduu.ai/atlas). v1 may still send you to the web app for some bundle payments; the destination is fully in-CLI purchase (see [Roadmap](#roadmap)).

Source repository: [bitbucket.org/nuuduu/nuuduu](https://bitbucket.org/nuuduu/nuuduu)

## Installation

```bash
# Library only
pip install nuuduu

# Library + CLI command (quote brackets so the shell does not treat them as globs)
pip install 'nuuduu[cli]'
```

Development install:

```bash
git clone git@bitbucket.org:nuuduu/nuuduu.git
cd nuuduu
uv sync --all-extras
```

## How it works

```text
Atlas library                    Your machine
─────────────                    ────────────
  search  ──→  find episodes
  bundle  ──→  package as LeRobot / MCAP / HEVC dataset
  sync    ──→  download  ──→  PyTorch / LeRobot training dataset

  request ──→  collect new episodes (when library is short)
       ↑
       └── triggered automatically by bundle --min-episodes
```

**Typical path — library has enough data:**

```bash
nuuduu episodes search --text "shirt folding"   # explore + see total price
nuuduu episodes bundle --text "shirt folding"   # purchase + package
nuuduu sync                                     # download locally
```

**When you need more than the library has:**

```bash
nuuduu episodes bundle --text "shirt folding" --country fi,ee --min-episodes 10000
# bundles 6 000 available episodes, requests collection of the remaining 4 000
nuuduu sync   # run again as bundles become ready
```

The default output format is **LeRobot v2.1**, ready for PyTorch training via [LeRobot](https://github.com/huggingface/lerobot) or your own data loader.

## Quick start (CLI)

```bash
nuuduu auth login

# 1. Search the Atlas library
nuuduu episodes search --text "pick up cup"

# 2. Bundle into a training dataset (confirms total price before purchase)
nuuduu episodes bundle --text "pick up cup"

# 3. Download to your local dataset directory
nuuduu sync

# 4. Train — LeRobot example
python -c "
from lerobot.common.datasets.lerobot_dataset import LeRobotDataset
dataset = LeRobotDataset('local', root='~/.cache/huggingface/lerobot/nuuduu-atlas/<download-uuid>')
print(len(dataset))
"
```

Need episodes that are not in the library yet? Use `--min-episodes` to bundle what's available and request collection of the rest, or request directly:

```bash
nuuduu episodes bundle --text "shirt folding" --min-episodes 10000 --country fi,ee
nuuduu request --text "pick and place red blocks" --episodes 1000 --country fi,ee
```

## Quick start (library)

```python
from nuuduu import NuuduuAtlas

atlas = NuuduuAtlas.from_config()

# Search the Atlas library
episodes = atlas.search_episodes(text="shirt folding", country="fi,ee", limit=100)
print(f"Found {len(episodes)} episodes")

# Bundle available episodes; automatically request collection for any shortfall
bundle = atlas.bundle_episodes(
    text="shirt folding",
    country="fi,ee",
    min_episodes=10000,
    confirm_request=True,
)
print(f"{bundle.bundled_episodes} bundled, {bundle.requested_episodes} requested")

# Download ready bundles to a local PyTorch-ready dataset
result = atlas.sync(format="lerobot")
for item in result.synced:
    print(item.path)  # e.g. .../lerobot/<uuid>/
```

## Authentication

Use the same email and password as your [Atlas web app](https://nuuduu.ai/atlas) account. The CLI authenticates against the API at `https://nuuduu.com` with `type=api`.

Credentials are stored at `~/.config/nuuduu/config.toml` with **0600** permissions. The CLI refuses to read config files that are group- or world-readable.

| Method | Usage |
|---|---|
| Config file | `api_token = "..."` in `~/.config/nuuduu/config.toml` |
| Environment | `export NUUDUU_API_TOKEN="..."` |
| Programmatic | `NuuduuAtlas(token="...")` |

```bash
nuuduu auth login              # email + password → saves token
nuuduu auth logout             # clear saved token
nuuduu auth token set TOKEN    # set token manually
nuuduu auth status             # show masked token status
```

Tokens expire after 24 hours in production. On HTTP 401, run `nuuduu auth login` again.

## Dataset directory

Resolved automatically via priority chain:

1. `--dataset-dir` CLI flag
2. `NUUDUU_DATASET_DIR` environment variable
3. `dataset_dir` in config file
4. `HF_LEROBOT_HOME/nuuduu-atlas` (LeRobot ecosystem default)
5. Project `.env`, training configs, or `./datasets/nuuduu-atlas`
6. Fallback: `~/.cache/huggingface/lerobot/nuuduu-atlas`

```bash
nuuduu config show   # shows resolved path and source
```

Local layout:

```
{dataset_dir}/
  .nuuduu/manifest.json
  lerobot/{download_uuid}/     # LeRobot v2.1 dataset
  mcap/{download_uuid}/        # per-episode .mcap files
  hevc/{download_uuid}/        # per-episode .mp4 files
```

## CLI reference

```bash
nuuduu --help
nuuduu --version

nuuduu auth login
nuuduu auth logout
nuuduu auth token set TOKEN
nuuduu auth status

nuuduu config show
nuuduu config set KEY VALUE

nuuduu sync [--dataset-dir PATH] [--format lerobot|mcap|hevc|all] [--dry-run] [--verify]

nuuduu episodes search [--text QUERY] [--country fi,ee] [--limit N]
nuuduu episodes latest [--limit N]
nuuduu episodes bundle [--text QUERY] [--country fi,ee] [--limit N] \
  [--min-episodes N] [--format lerobot|mcap|hevc] [--uuids UUID[,UUID...]] \
  [--wait] [--yes]

nuuduu request --text "TASK" --episodes N [--country fi,ee] [--yes]
nuuduu request list
```

Add `--json` to any command for machine-readable output.

### Shared search options

These options work the same on `episodes search`, `episodes bundle`, and `request`:

| Option | Description |
|---|---|
| `--text` / `-t` | Semantic search query / task description |
| `--country` / `-c` | Comma-separated [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes (e.g. `fi,ee,de`); lowercased before sending |
| `--limit` / `-n` | Max episodes to search (search and bundle only; defaults to 20) |

`episodes search` prints a **total price** line after the results table. `episodes bundle` shows the same total and prompts `Proceed with bundle purchase? [Y/n]` before creating the bundle.

### Bundle-specific options

| Option | Description |
|---|---|
| `--format` / `-f` | Output format: `lerobot` (default), `mcap`, or `hevc` |
| `--min-episodes` | Target episode count; bundles what's available, then quotes a collection request for any shortfall (requires `--text`) |
| `--uuids` / `-u` | Comma-separated episode UUIDs (skips search when you already know them) |
| `--wait` | Poll until the bundle job is ready |
| `--yes` / `-y` | Skip bundle purchase and collection request confirmation prompts |

## Roadmap

**v1 (today):** Search the Atlas library, bundle episodes into a training dataset, and download locally with `nuuduu sync`. Some paid bundles still require completing payment in the [Atlas web app](https://nuuduu.ai/atlas) before download.

**Destination UX** — order new data entirely from the terminal:

```bash
nuuduu request --text "pick and place red blocks" --episodes 1000
```

```
Estimated price: €742
Available immediately: 681 episodes
To collect: 319 episodes

Purchase using Acme Robotics account? [Y/n]
```

After confirmation, Atlas fulfills the order and `nuuduu sync` materializes bundles locally. No browser step.

## Output formats

Bundles are written to your local dataset directory in one of these formats:

| Format | Contents | Best for |
|---|---|---|
| `lerobot` (default) | LeRobot v2.1 dataset (meta/, data/, videos/) | **PyTorch / LeRobot training** |
| `mcap` | Per-episode MCAP files | ROS 2 / MCAP tooling |
| `hevc` | Per-episode HEVC MP4 files | Video analysis pipelines |

## Integrity verification

When the Atlas API provides a `hash` field (SHA256 of the bundle zip), `nuuduu sync`:

1. Verifies the downloaded zip before extracting
2. Stores the hash in `.nuuduu/manifest.json`
3. Skips re-download when the local hash matches

```bash
nuuduu sync --verify   # re-check local bundles without downloading
```

## Library API

| Class / function | Purpose |
|---|---|
| `NuuduuAtlas` | High-level facade (recommended) |
| `AtlasClient` | Low-level HTTP API client |
| `SyncEngine` | Sync and verify engine |
| `NuuduuConfig` | Config load/save |
| `resolve_dataset_dir()` | Dataset path resolution |
| `EpisodeSearchOptions` | Shared search parameters |
| `BundleResult` | Result of `bundle_episodes()` (download + optional collection request) |

Key methods on `NuuduuAtlas`:

| Method | Purpose |
|---|---|
| `search_episodes(text, country, limit)` | Search ready episodes |
| `bundle_episodes(text, country, min_episodes, ...)` | Bundle + optional collection request |
| `request_episodes(task, episodes, country)` | Quote/confirm a collection request |
| `sync(format, ...)` | Download ready bundles locally |
| `login()` / `logout()` | Manage API credentials |

Exceptions: `AuthError`, `ApiError`, `IntegrityError`, `ConfigError`, `NotFoundError`

Progress callbacks for sync:

```python
from nuuduu.types import SyncProgressEvent

def on_progress(event: SyncProgressEvent) -> None:
    print(event.phase, event.bytes_done, event.bytes_total)

atlas.sync(on_progress=on_progress)
```

## Troubleshooting

| Problem | Solution |
|---|---|
| HTTP 401 | Run `nuuduu auth login` — token may have expired |
| Config permission error | Run `chmod 600 ~/.config/nuuduu/config.toml` |
| Payment required (v1) | Complete payment at https://nuuduu.ai/atlas, then `nuuduu sync`. Future: in-CLI via `nuuduu request` |
| SHA256 mismatch | Re-run `nuuduu sync` to re-download the bundle |
| Request API 404 | `nuuduu request` API not live yet — CLI shows the destination UX preview |
| `--min-episodes` needs `--text` | Provide a task description for the collection request |

## Development

```bash
uv sync --all-extras
uv run pytest
uv build
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for release and contribution guidelines.

CI runs on [Bitbucket Pipelines](https://bitbucket.org/nuuduu/nuuduu/pipelines) for every push and pull request.

## Publishing

```bash
# Bump version in pyproject.toml and CHANGELOG.md
uv build
git tag v0.4.0 && git push origin v0.4.0   # triggers Bitbucket Pipeline PyPI publish
```

One-time setup: add a secured `PYPI_TOKEN` repository variable in
**Bitbucket → Repository settings → Pipelines → Repository variables**.

## Legal

Copyright © 2026 Nuuduu UAB. Licensed under the [MIT License](LICENSE).

**Nuuduu®** is a registered trademark of Nuuduu UAB. All rights reserved.
The Nuuduu name and logo may not be used to imply endorsement or affiliation
without prior written permission from Nuuduu UAB.
