Metadata-Version: 2.4
Name: nuuduu
Version: 0.4.0
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)

Python SDK and CLI for [Nuuduu Atlas](https://nuuduu.ai/atlas) — search, bundle, request, and sync robotics training datasets (LeRobot, MCAP, HEVC) to your local machine for PyTorch / LeRobot workflows.

| | 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 via `nuuduu request` (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
```

## Workflow

```text
search  →  bundle  →  sync
              ↓
         request (if you need more episodes than the library has)
```

1. **Search** — find episodes matching a task description and optional country filter
2. **Bundle** — server packages matching episodes into a downloadable archive (`lerobot` by default)
3. **Sync** — download ready bundles to your local dataset directory
4. **Request** — order new episodes to be collected when the library does not have enough (`--min-episodes` on bundle does this automatically)

## Quick start (CLI)

```bash
# Authenticate (saves token to ~/.config/nuuduu/config.toml, mode 0600)
nuuduu auth login

# Search episodes
nuuduu episodes search --text "pick up cup"

# Bundle episodes matching a search (server-side packaging job)
nuuduu episodes bundle --text "pick up cup"

# Need 10k episodes but the library only has 6k — bundle what's available, request the rest
nuuduu episodes bundle --text "shirt folding" --country fi,ee --min-episodes 10000

# Request new training data directly (quote + confirm — API coming soon)
nuuduu request --text "pick and place red blocks" --episodes 1000 --country fi,ee

# Sync ready bundles to your local dataset directory
nuuduu sync

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

## Quick start (library)

```python
from nuuduu import NuuduuAtlas

atlas = NuuduuAtlas.from_config()

# Bundle what's in the library; request any shortfall
bundle = atlas.bundle_episodes(
    text="shirt folding",
    country="fi,ee",
    min_episodes=10000,
    confirm_request=True,
)
print(bundle.bundled_episodes, "bundled,", bundle.requested_episodes, "requested")

result = atlas.sync(format="lerobot")
for item in result.synced:
    print(item.path)
```

## 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) |

### 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 collection request confirmation when `--min-episodes` triggers a request |

## Roadmap

**v1 (today):** Search episodes, bundle matching episodes on the server, then `nuuduu sync` to download ready bundles locally. Some paid bundles still require completing payment in the [Atlas web app](https://nuuduu.ai/atlas) before `nuuduu sync`.

**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.

## Download formats

| Format | Contents | Best for |
|---|---|---|
| `lerobot` | 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.
