kiteml-cli 0.4.0__tar.gz → 0.5.0__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.
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/PKG-INFO +22 -15
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/README.md +19 -12
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/pyproject.toml +2 -2
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/__init__.py +1 -1
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/augment.py +13 -6
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/evaluations.py +39 -15
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/client.py +11 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/mcp/server.py +31 -18
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/.gitignore +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/archive.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/auth.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/__init__.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/__init__.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/auth_cmd.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/config_cmd.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/rl.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/commands/twin.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/cli/main.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/config.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/mcp/__init__.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/mcp/common.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/output.py +0 -0
- {kiteml_cli-0.4.0 → kiteml_cli-0.5.0}/src/kite_cli/progress.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: kiteml-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Kite CLI and MCP server: train robot policies with reinforcement learning in simulation, augment LeRobot datasets, and build MuJoCo digital twins, from your terminal or an AI agent.
|
|
5
5
|
Project-URL: Homepage, https://kiteml.com
|
|
6
|
-
Project-URL: Documentation, https://
|
|
7
|
-
Project-URL: API reference, https://
|
|
6
|
+
Project-URL: Documentation, https://docs.kiteml.com/tools/cli-and-mcp
|
|
7
|
+
Project-URL: API reference, https://docs.kiteml.com/platform-api/overview
|
|
8
8
|
Project-URL: Dashboard, https://app.kiteml.com
|
|
9
9
|
Project-URL: MCP server, https://mcp.kiteml.com/mcp
|
|
10
10
|
Project-URL: Blog, https://kiteml.com/blog
|
|
@@ -43,8 +43,8 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
# Kite CLI and MCP server
|
|
44
44
|
|
|
45
45
|
Robot learning on cloud GPUs, from your terminal or your AI agent. Kite trains robot policies with
|
|
46
|
-
reinforcement learning in simulation, augments LeRobot datasets, and rebuilds
|
|
47
|
-
twins. This package gives you the `kite` command line and the `kite-mcp` server for Claude, Cursor, and any
|
|
46
|
+
reinforcement learning in simulation, evaluates policies on Kite SDK specs, augments LeRobot datasets, and rebuilds
|
|
47
|
+
robot episodes as MuJoCo digital twins. This package gives you the `kite` command line and the `kite-mcp` server for Claude, Cursor, and any
|
|
48
48
|
Model Context Protocol (MCP) client.
|
|
49
49
|
|
|
50
50
|
<!-- mcp-name: com.kiteml/kite -->
|
|
@@ -52,11 +52,11 @@ Model Context Protocol (MCP) client.
|
|
|
52
52
|
- **RL training in simulation.** Describe a behavior in plain English. Kite writes the task spec, checks its
|
|
53
53
|
rewards for free, and trains the policy on a GPU. You get the policy as ONNX, the MuJoCo scene it trained in, and
|
|
54
54
|
clips.
|
|
55
|
-
- **
|
|
55
|
+
- **RL verdicts.** Every RL run ends with a report: a pass, needs_review or fail verdict from measured checks
|
|
56
56
|
(falls per minute, survival, command tracking, gait, posture), plus a vision model's read of the clip.
|
|
57
|
-
- **Dataset augmentation.** Relight a LeRobot dataset
|
|
58
|
-
it with video augmentation. Output is a standard LeRobot dataset (Parquet +
|
|
59
|
-
Hugging Face.
|
|
57
|
+
- **Dataset augmentation.** Relight a LeRobot dataset on Hugging Face or in a public S3 bucket to match a deployment
|
|
58
|
+
scene, or generate new variations of it with video augmentation. Output is a standard LeRobot dataset (Parquet +
|
|
59
|
+
MP4), downloaded or pushed to Hugging Face.
|
|
60
60
|
- **Digital twins (beta).** Point Kite at one episode of a LeRobot dataset and get an interactable MuJoCo scene of the
|
|
61
61
|
room, with the objects the robot handles built to size.
|
|
62
62
|
- **Evaluations (beta).** Evaluate a policy by running a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU
|
|
@@ -106,7 +106,7 @@ lerobot/pusht to look like this photo".
|
|
|
106
106
|
| --- | --- |
|
|
107
107
|
| `kite_rl_catalog`, `kite_rl_plan`, `kite_rl_validate` | See which robots and objectives RL runs support, and plan and check a training spec for free |
|
|
108
108
|
| `kite_rl_train`, `kite_rl_fork`, `kite_rl_cancel` | Start, branch from, and stop RL runs |
|
|
109
|
-
| `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's
|
|
109
|
+
| `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's verdict |
|
|
110
110
|
| `kite_augment_create`, `kite_augment_status`, `kite_augment_list`, `kite_augment_cancel` | Augment LeRobot datasets |
|
|
111
111
|
| `kite_twin_validate`, `kite_twin_create`, `kite_twin_status`, `kite_twin_list`, `kite_twin_cancel`, `kite_twin_resume` | Build MuJoCo digital twins (beta) |
|
|
112
112
|
| `kite_eval_validate`, `kite_eval_run`, `kite_eval_status`, `kite_eval_list`, `kite_eval_report`, `kite_eval_logs`, `kite_eval_cancel`, `kite_eval_resume` | Evaluate a policy on a Kite SDK spec on Kite's machines and read the scorecard (beta) |
|
|
@@ -132,6 +132,9 @@ kite augment create --repo-id lerobot/pusht \
|
|
|
132
132
|
kite augment download aug_... -o ./pusht-marble
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
`--repo-id` takes a Hugging Face id (`lerobot/pusht`), its `huggingface.co/datasets/` URL, or an S3 link to the
|
|
136
|
+
dataset's folder in a public bucket (`s3://bucket/dataset/`).
|
|
137
|
+
|
|
135
138
|
Build a digital twin (beta, enabled per account):
|
|
136
139
|
|
|
137
140
|
```bash
|
|
@@ -145,16 +148,20 @@ kite eval validate specs/smoke.yaml # free: the plan and what a run r
|
|
|
145
148
|
kite eval run specs/smoke.yaml --out ./runs # -> ./runs/eval_.../scorecard.json
|
|
146
149
|
```
|
|
147
150
|
|
|
151
|
+
A file too big to send, such as a checkpoint's weights, can come from the Hugging Face Hub or a public S3 bucket:
|
|
152
|
+
`--hf PATH=OWNER/NAME@COMMIT:FILE` or `--hf PATH=<the file's huggingface.co URL>`, and `--s3 PATH=s3://BUCKET/KEY`,
|
|
153
|
+
PATH being the path the spec names.
|
|
154
|
+
|
|
148
155
|
Every command prints JSON (`{"ok": true, "data": ...}`) so scripts and agents can parse it. Run `kite --help` for
|
|
149
156
|
the full list.
|
|
150
157
|
|
|
151
158
|
## Links
|
|
152
159
|
|
|
153
|
-
- Docs: [CLI and MCP](https://
|
|
154
|
-
[RL runs](https://
|
|
155
|
-
[Augmentations](https://
|
|
156
|
-
[Twins](https://
|
|
157
|
-
[Evaluations](https://
|
|
160
|
+
- Docs: [CLI and MCP](https://docs.kiteml.com/tools/cli-and-mcp) ·
|
|
161
|
+
[RL runs](https://docs.kiteml.com/platform-api/rl-runs) ·
|
|
162
|
+
[Augmentations](https://docs.kiteml.com/platform-api/augmentation) ·
|
|
163
|
+
[Twins](https://docs.kiteml.com/platform-api/twins) ·
|
|
164
|
+
[Evaluations](https://docs.kiteml.com/platform-api/evaluations)
|
|
158
165
|
- Dashboard: [app.kiteml.com](https://app.kiteml.com)
|
|
159
166
|
- Website: [kiteml.com](https://kiteml.com)
|
|
160
167
|
- Support: [raul@kiteml.com](mailto:raul@kiteml.com)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Kite CLI and MCP server
|
|
2
2
|
|
|
3
3
|
Robot learning on cloud GPUs, from your terminal or your AI agent. Kite trains robot policies with
|
|
4
|
-
reinforcement learning in simulation, augments LeRobot datasets, and rebuilds
|
|
5
|
-
twins. This package gives you the `kite` command line and the `kite-mcp` server for Claude, Cursor, and any
|
|
4
|
+
reinforcement learning in simulation, evaluates policies on Kite SDK specs, augments LeRobot datasets, and rebuilds
|
|
5
|
+
robot episodes as MuJoCo digital twins. This package gives you the `kite` command line and the `kite-mcp` server for Claude, Cursor, and any
|
|
6
6
|
Model Context Protocol (MCP) client.
|
|
7
7
|
|
|
8
8
|
<!-- mcp-name: com.kiteml/kite -->
|
|
@@ -10,11 +10,11 @@ Model Context Protocol (MCP) client.
|
|
|
10
10
|
- **RL training in simulation.** Describe a behavior in plain English. Kite writes the task spec, checks its
|
|
11
11
|
rewards for free, and trains the policy on a GPU. You get the policy as ONNX, the MuJoCo scene it trained in, and
|
|
12
12
|
clips.
|
|
13
|
-
- **
|
|
13
|
+
- **RL verdicts.** Every RL run ends with a report: a pass, needs_review or fail verdict from measured checks
|
|
14
14
|
(falls per minute, survival, command tracking, gait, posture), plus a vision model's read of the clip.
|
|
15
|
-
- **Dataset augmentation.** Relight a LeRobot dataset
|
|
16
|
-
it with video augmentation. Output is a standard LeRobot dataset (Parquet +
|
|
17
|
-
Hugging Face.
|
|
15
|
+
- **Dataset augmentation.** Relight a LeRobot dataset on Hugging Face or in a public S3 bucket to match a deployment
|
|
16
|
+
scene, or generate new variations of it with video augmentation. Output is a standard LeRobot dataset (Parquet +
|
|
17
|
+
MP4), downloaded or pushed to Hugging Face.
|
|
18
18
|
- **Digital twins (beta).** Point Kite at one episode of a LeRobot dataset and get an interactable MuJoCo scene of the
|
|
19
19
|
room, with the objects the robot handles built to size.
|
|
20
20
|
- **Evaluations (beta).** Evaluate a policy by running a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU
|
|
@@ -64,7 +64,7 @@ lerobot/pusht to look like this photo".
|
|
|
64
64
|
| --- | --- |
|
|
65
65
|
| `kite_rl_catalog`, `kite_rl_plan`, `kite_rl_validate` | See which robots and objectives RL runs support, and plan and check a training spec for free |
|
|
66
66
|
| `kite_rl_train`, `kite_rl_fork`, `kite_rl_cancel` | Start, branch from, and stop RL runs |
|
|
67
|
-
| `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's
|
|
67
|
+
| `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's verdict |
|
|
68
68
|
| `kite_augment_create`, `kite_augment_status`, `kite_augment_list`, `kite_augment_cancel` | Augment LeRobot datasets |
|
|
69
69
|
| `kite_twin_validate`, `kite_twin_create`, `kite_twin_status`, `kite_twin_list`, `kite_twin_cancel`, `kite_twin_resume` | Build MuJoCo digital twins (beta) |
|
|
70
70
|
| `kite_eval_validate`, `kite_eval_run`, `kite_eval_status`, `kite_eval_list`, `kite_eval_report`, `kite_eval_logs`, `kite_eval_cancel`, `kite_eval_resume` | Evaluate a policy on a Kite SDK spec on Kite's machines and read the scorecard (beta) |
|
|
@@ -90,6 +90,9 @@ kite augment create --repo-id lerobot/pusht \
|
|
|
90
90
|
kite augment download aug_... -o ./pusht-marble
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
`--repo-id` takes a Hugging Face id (`lerobot/pusht`), its `huggingface.co/datasets/` URL, or an S3 link to the
|
|
94
|
+
dataset's folder in a public bucket (`s3://bucket/dataset/`).
|
|
95
|
+
|
|
93
96
|
Build a digital twin (beta, enabled per account):
|
|
94
97
|
|
|
95
98
|
```bash
|
|
@@ -103,16 +106,20 @@ kite eval validate specs/smoke.yaml # free: the plan and what a run r
|
|
|
103
106
|
kite eval run specs/smoke.yaml --out ./runs # -> ./runs/eval_.../scorecard.json
|
|
104
107
|
```
|
|
105
108
|
|
|
109
|
+
A file too big to send, such as a checkpoint's weights, can come from the Hugging Face Hub or a public S3 bucket:
|
|
110
|
+
`--hf PATH=OWNER/NAME@COMMIT:FILE` or `--hf PATH=<the file's huggingface.co URL>`, and `--s3 PATH=s3://BUCKET/KEY`,
|
|
111
|
+
PATH being the path the spec names.
|
|
112
|
+
|
|
106
113
|
Every command prints JSON (`{"ok": true, "data": ...}`) so scripts and agents can parse it. Run `kite --help` for
|
|
107
114
|
the full list.
|
|
108
115
|
|
|
109
116
|
## Links
|
|
110
117
|
|
|
111
|
-
- Docs: [CLI and MCP](https://
|
|
112
|
-
[RL runs](https://
|
|
113
|
-
[Augmentations](https://
|
|
114
|
-
[Twins](https://
|
|
115
|
-
[Evaluations](https://
|
|
118
|
+
- Docs: [CLI and MCP](https://docs.kiteml.com/tools/cli-and-mcp) ·
|
|
119
|
+
[RL runs](https://docs.kiteml.com/platform-api/rl-runs) ·
|
|
120
|
+
[Augmentations](https://docs.kiteml.com/platform-api/augmentation) ·
|
|
121
|
+
[Twins](https://docs.kiteml.com/platform-api/twins) ·
|
|
122
|
+
[Evaluations](https://docs.kiteml.com/platform-api/evaluations)
|
|
116
123
|
- Dashboard: [app.kiteml.com](https://app.kiteml.com)
|
|
117
124
|
- Website: [kiteml.com](https://kiteml.com)
|
|
118
125
|
- Support: [raul@kiteml.com](mailto:raul@kiteml.com)
|
|
@@ -43,8 +43,8 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[project.urls]
|
|
45
45
|
Homepage = "https://kiteml.com"
|
|
46
|
-
Documentation = "https://
|
|
47
|
-
"API reference" = "https://
|
|
46
|
+
Documentation = "https://docs.kiteml.com/tools/cli-and-mcp"
|
|
47
|
+
"API reference" = "https://docs.kiteml.com/platform-api/overview"
|
|
48
48
|
Dashboard = "https://app.kiteml.com"
|
|
49
49
|
"MCP server" = "https://mcp.kiteml.com/mcp"
|
|
50
50
|
Blog = "https://kiteml.com/blog"
|
|
@@ -38,10 +38,11 @@ def reference_image(path: str, camera: Optional[str] = None) -> dict:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def scene_config(episodes, model: Optional[str], variants: Optional[int] = None,
|
|
41
|
-
preview: Optional[bool] = None, allow_video_fallback: Optional[bool] = None
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
preview: Optional[bool] = None, allow_video_fallback: Optional[bool] = None,
|
|
42
|
+
video_check: Optional[str] = None) -> dict:
|
|
43
|
+
"""The /v1 ``config`` for an episode selection ("all" or a count), the scene engines' options and the video
|
|
44
|
+
check's mode: background / world augment whole datasets and take ``episodes``; every other model takes a count as
|
|
45
|
+
``episode_count`` ("all" sends none: relight covers every episode, video and auto keep their default)."""
|
|
45
46
|
config: dict = {}
|
|
46
47
|
if episodes is not None:
|
|
47
48
|
if str(episodes) != "all" and not str(episodes).isdigit():
|
|
@@ -56,12 +57,14 @@ def scene_config(episodes, model: Optional[str], variants: Optional[int] = None,
|
|
|
56
57
|
config["preview_first"] = preview
|
|
57
58
|
if allow_video_fallback:
|
|
58
59
|
config["allow_video_fallback"] = True
|
|
60
|
+
if video_check:
|
|
61
|
+
config["video_check"] = video_check
|
|
59
62
|
return config
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
@app.command()
|
|
63
66
|
def create(
|
|
64
|
-
repo_id: str = typer.Option(..., "--repo-id", help="Source
|
|
67
|
+
repo_id: str = typer.Option(..., "--repo-id", help="Source LeRobot dataset: a HF id (lerobot/pusht) or huggingface.co/datasets/ URL, or an S3 link (s3://bucket/dataset/ or its https URL; public buckets)"),
|
|
65
68
|
instructions: str = typer.Option(..., "-i", "--instructions", help="Augmentation instruction"),
|
|
66
69
|
episodes: Optional[str] = typer.Option(
|
|
67
70
|
None, "-n", "--episodes",
|
|
@@ -89,6 +92,10 @@ def create(
|
|
|
89
92
|
help="auto: use generative video (far more tokens, no pixel guarantee) where no scene engine can run, "
|
|
90
93
|
"instead of failing",
|
|
91
94
|
),
|
|
95
|
+
video_check: Optional[str] = typer.Option(
|
|
96
|
+
None, "--video-check", help="Generative video: standard (default: rejects a changed robot or a missing object; "
|
|
97
|
+
"the rest ship as warnings) | strict (every problem rejects) | off (records warnings, never rejects)",
|
|
98
|
+
),
|
|
92
99
|
reference_images: Optional[List[str]] = typer.Option(
|
|
93
100
|
None, "--reference-image", help="Photo of the target look, [camera=]PATH (repeatable)"
|
|
94
101
|
),
|
|
@@ -107,7 +114,7 @@ def create(
|
|
|
107
114
|
body: dict = {
|
|
108
115
|
"source": {"type": "huggingface", "repo_id": repo_id},
|
|
109
116
|
"instructions": instructions,
|
|
110
|
-
"config": scene_config(episodes, model, variants, preview, allow_video_fallback),
|
|
117
|
+
"config": scene_config(episodes, model, variants, preview, allow_video_fallback, video_check),
|
|
111
118
|
"output": {"type": output, "private": private},
|
|
112
119
|
}
|
|
113
120
|
if hf_repo:
|
|
@@ -16,7 +16,8 @@ machine otherwise. (Until 2026-10-18 this was `kite sim`, over /v1/sim_runs.)
|
|
|
16
16
|
SPEC is the file `kite run` takes (kite_spec_version "2"). The command sends its text, asks the API which paths it
|
|
17
17
|
names (`files_required`), and sends each from this disk: a file or folder Kite ships by its sha256 alone, anything
|
|
18
18
|
else inline (about 15 MB in all). A file on the Hugging Face Hub, such as a checkpoint's weights, goes as a
|
|
19
|
-
reference: `--hf PATH=OWNER/NAME@COMMIT:FILE`, PATH as the spec names it (a folder's file as FOLDER/FILE)
|
|
19
|
+
reference: `--hf PATH=OWNER/NAME@COMMIT:FILE`, PATH as the spec names it (a folder's file as FOLDER/FILE); an object
|
|
20
|
+
in a public S3 bucket as `--s3 PATH=s3://BUCKET/KEY`.
|
|
20
21
|
Where runs go: --out DIR, else $KITE_EVAL_RUNS_DIR, else ./runs (`~` works). Paths printed are absolute. A run
|
|
21
22
|
directory extracts to <out>/<run_id>/; the SDK's `kite verify <out>/<run_id> --evals DIR` checks it, DIR being the
|
|
22
23
|
evaluators the spec named (the run records where its job read them, not your folder).
|
|
@@ -56,7 +57,10 @@ _HF = re.compile(r"(?P<repo>[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]
|
|
|
56
57
|
r":(?P<path>.+)")
|
|
57
58
|
|
|
58
59
|
_SET_HELP = "FIELD=VALUE, as `kite run --set` (repeatable)"
|
|
59
|
-
_HF_HELP = "PATH=OWNER/NAME@COMMIT:FILE
|
|
60
|
+
_HF_HELP = ("PATH=OWNER/NAME@COMMIT:FILE, or PATH=URL (https://huggingface.co/OWNER/NAME/blob/REVISION/FILE; Kite pins a "
|
|
61
|
+
"branch to its commit): the spec's PATH is that file of a public Hugging Face repo (repeatable)")
|
|
62
|
+
_S3_HELP = ("PATH=s3://BUCKET/KEY (or its https://BUCKET.s3.amazonaws.com/KEY URL): the spec's PATH is that object of a "
|
|
63
|
+
"public S3 bucket, pinned to the object it is when the run is created (repeatable)")
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
@app.command()
|
|
@@ -64,12 +68,13 @@ def validate(
|
|
|
64
68
|
spec: Path = typer.Argument(..., help="The spec file, e.g. specs/smoke.yaml"),
|
|
65
69
|
set_: Optional[List[str]] = typer.Option(None, "--set", help=_SET_HELP),
|
|
66
70
|
hf: Optional[List[str]] = typer.Option(None, "--hf", help=_HF_HELP),
|
|
71
|
+
s3: Optional[List[str]] = typer.Option(None, "--s3", help=_S3_HELP),
|
|
67
72
|
kite_version: Optional[str] = typer.Option(None, "--kite-version", help="The Kite release you expect"),
|
|
68
73
|
):
|
|
69
74
|
"""Free dry run of a run: the spec plans, every file it names is sent, and the price. Creates nothing."""
|
|
70
75
|
try:
|
|
71
76
|
client = KiteClient(timeout=_SLOW_S)
|
|
72
|
-
_, validation, sent = request(client, spec, set_ or [], hf_refs(hf or []), kite_version)
|
|
77
|
+
_, validation, sent = request(client, spec, set_ or [], {**hf_refs(hf or []), **s3_refs(s3 or [])}, kite_version)
|
|
73
78
|
print_json({"ok": True, "data": validation, "files": sent})
|
|
74
79
|
except KiteAPIError as e:
|
|
75
80
|
print_error(e.message, code=e.code, details=e.details)
|
|
@@ -80,6 +85,7 @@ def run(
|
|
|
80
85
|
spec: Path = typer.Argument(..., help="The spec file, e.g. specs/smoke.yaml"),
|
|
81
86
|
set_: Optional[List[str]] = typer.Option(None, "--set", help=_SET_HELP),
|
|
82
87
|
hf: Optional[List[str]] = typer.Option(None, "--hf", help=_HF_HELP),
|
|
88
|
+
s3: Optional[List[str]] = typer.Option(None, "--s3", help=_S3_HELP),
|
|
83
89
|
max_minutes: Optional[int] = typer.Option(
|
|
84
90
|
None, "--max-minutes", help="The run's cap, 1-720: what it reserves and the most it can cost "
|
|
85
91
|
"(default 60 on CPU, 120 on an L4)"),
|
|
@@ -100,7 +106,7 @@ def run(
|
|
|
100
106
|
key = idempotency_key or str(uuid.uuid4())
|
|
101
107
|
try:
|
|
102
108
|
client = KiteClient(timeout=_SLOW_S)
|
|
103
|
-
body, _, _ = request(client, spec, set_ or [], hf_refs(hf or []), kite_version)
|
|
109
|
+
body, _, _ = request(client, spec, set_ or [], {**hf_refs(hf or []), **s3_refs(s3 or [])}, kite_version)
|
|
104
110
|
options = {k: v for k, v in (("max_minutes", max_minutes), ("no_eval", no_eval),
|
|
105
111
|
("verify_determinism", verify_determinism)) if v}
|
|
106
112
|
body = {**body, **({"options": options} if options else {}), **({"label": label} if label else {})}
|
|
@@ -242,10 +248,11 @@ def resume(run_id: str = typer.Argument(..., help="Evaluation id (eval_...)")):
|
|
|
242
248
|
|
|
243
249
|
# --- the request: the spec and every path it names ------------------------------------------------------------------
|
|
244
250
|
|
|
245
|
-
def request(client: KiteClient, spec: Path, overrides: List[str],
|
|
251
|
+
def request(client: KiteClient, spec: Path, overrides: List[str], remote: Dict[str, dict],
|
|
246
252
|
kite_version: Optional[str] = None) -> Tuple[dict, dict, Dict[str, str]]:
|
|
247
253
|
"""The request body for SPEC with every path it names, the validation that accepted it, and how each path went
|
|
248
|
-
(`sha256`, `inline`, `hf`).
|
|
254
|
+
(`sha256`, `inline`, `hf`, `s3`). `remote` maps the paths that are an `hf` file or an `s3` object to their
|
|
255
|
+
reference ({"hf": …} or {"s3": …}).
|
|
249
256
|
|
|
250
257
|
The API names what it lacks: `files_required` lists the paths the spec names that the request does not carry,
|
|
251
258
|
and `file_unavailable` on `files.<path>` says a hash Kite does not ship. A path first goes by its hash alone, so
|
|
@@ -253,12 +260,12 @@ def request(client: KiteClient, spec: Path, overrides: List[str], hf: Dict[str,
|
|
|
253
260
|
text, spec_path, base = _spec(spec)
|
|
254
261
|
files: Dict[str, dict] = {}
|
|
255
262
|
sent: Dict[str, str] = {}
|
|
256
|
-
for key, ref in
|
|
257
|
-
files[key] =
|
|
263
|
+
for key, ref in remote.items():
|
|
264
|
+
files[key] = dict(ref)
|
|
258
265
|
local = _local(base, key)
|
|
259
|
-
if local.is_file(): # pin the bytes this machine has: the Hub's file must be the same
|
|
266
|
+
if local.is_file(): # pin the bytes this machine has: the Hub's file or the object must be the same
|
|
260
267
|
files[key]["sha256"] = _file_sha256(local)
|
|
261
|
-
sent[key] =
|
|
268
|
+
sent[key] = next(iter(ref))
|
|
262
269
|
body: dict = {"spec_yaml": text, "spec_path": spec_path, "files": files}
|
|
263
270
|
if overrides:
|
|
264
271
|
body["overrides"] = list(overrides)
|
|
@@ -356,8 +363,8 @@ def _fits(key: str, raw_bytes: int, files: Dict[str, dict], biggest: List[Tuple[
|
|
|
356
363
|
if size > _INLINE_MAX or total > _BODY_MAX:
|
|
357
364
|
hint = "".join(f", {posixpath.join(key, rel)} {p.stat().st_size >> 20} MB" for rel, p in biggest)
|
|
358
365
|
raise KiteAPIError(413, f"{key} is not a file Kite ships, and it is too big to send inline "
|
|
359
|
-
f"({raw_bytes >> 20} MB{hint}): put its large files on the Hugging Face Hub
|
|
360
|
-
"pass each with --hf PATH=OWNER/NAME@COMMIT:FILE", code="file_too_large",
|
|
366
|
+
f"({raw_bytes >> 20} MB{hint}): put its large files on the Hugging Face Hub or in a public "
|
|
367
|
+
"S3 bucket and pass each with --hf PATH=OWNER/NAME@COMMIT:FILE or --s3 PATH=s3://BUCKET/KEY", code="file_too_large",
|
|
361
368
|
details={"path": key})
|
|
362
369
|
|
|
363
370
|
|
|
@@ -401,11 +408,28 @@ def hf_refs(pairs: List[str]) -> Dict[str, dict]:
|
|
|
401
408
|
refs: Dict[str, dict] = {}
|
|
402
409
|
for pair in pairs:
|
|
403
410
|
key, _, ref = pair.partition("=")
|
|
404
|
-
|
|
411
|
+
ref = ref.strip()
|
|
412
|
+
if key and re.match(r"(https?://)?(www\.)?(huggingface|hf)\.co/", ref): # the API reads the URL
|
|
413
|
+
refs[key.strip()] = {"hf": ref}
|
|
414
|
+
continue
|
|
415
|
+
m = _HF.fullmatch(ref)
|
|
405
416
|
if not key or not m:
|
|
406
417
|
raise KiteAPIError(400, f"--hf {pair!r}: expected PATH=OWNER/NAME@COMMIT:FILE, with the full 40-character "
|
|
407
|
-
"commit", code="parameter_invalid",
|
|
408
|
-
|
|
418
|
+
"commit, or PATH=the file's huggingface.co URL", code="parameter_invalid",
|
|
419
|
+
details={"param": "hf"})
|
|
420
|
+
refs[key.strip()] = {"hf": {"repo": m["repo"], "revision": m["revision"], "path": m["path"]}}
|
|
421
|
+
return refs
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def s3_refs(pairs: List[str]) -> Dict[str, dict]:
|
|
425
|
+
"""PATH=S3_LINK pairs as {"s3": link} references; the API reads the link."""
|
|
426
|
+
refs: Dict[str, dict] = {}
|
|
427
|
+
for pair in pairs:
|
|
428
|
+
key, _, link = pair.partition("=")
|
|
429
|
+
if not key or not re.match(r"(?i)(s3://|https://)\S+", link.strip()):
|
|
430
|
+
raise KiteAPIError(400, f"--s3 {pair!r}: expected PATH=s3://BUCKET/KEY", code="parameter_invalid",
|
|
431
|
+
details={"param": "s3"})
|
|
432
|
+
refs[key.strip()] = {"s3": link.strip()}
|
|
409
433
|
return refs
|
|
410
434
|
|
|
411
435
|
|
|
@@ -161,6 +161,17 @@ class KiteClient:
|
|
|
161
161
|
for chunk in resp.iter_bytes(chunk_size=8192):
|
|
162
162
|
f.write(chunk)
|
|
163
163
|
|
|
164
|
+
def redirect_target(self, path: str) -> Optional[str]:
|
|
165
|
+
"""Where an API download path (a run's or a twin's archive) redirects: a fresh signed link, or None when the
|
|
166
|
+
API streams the file itself. Only the response's headers are read."""
|
|
167
|
+
with self._client.stream("GET", path, headers=self._base_headers(), follow_redirects=False) as resp:
|
|
168
|
+
if resp.is_redirect:
|
|
169
|
+
return resp.headers.get("location")
|
|
170
|
+
if resp.status_code >= 400:
|
|
171
|
+
resp.read()
|
|
172
|
+
raise _api_error(resp)
|
|
173
|
+
return None
|
|
174
|
+
|
|
164
175
|
def close(self) -> None:
|
|
165
176
|
self._client.close()
|
|
166
177
|
|
|
@@ -56,6 +56,23 @@ def _twin_source(dataset: str) -> dict:
|
|
|
56
56
|
return {"type": "huggingface", "repo_id": dataset}
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def _fetch_command(d: dict, fname: str, client: KiteClient) -> str:
|
|
60
|
+
"""The command that fetches a finished resource's archive. From Kite-Version 2026-10-12 `download.url` is this
|
|
61
|
+
API's path, which needs the key: ask it for the fresh signed link it redirects to, so an agent without the key
|
|
62
|
+
(the hosted server's OAuth) can fetch it directly. Without one, the API path with the key."""
|
|
63
|
+
if d.get("requires_auth"):
|
|
64
|
+
resolve = getattr(client, "redirect_target", None)
|
|
65
|
+
try:
|
|
66
|
+
signed = resolve(d["url"]) if resolve else None
|
|
67
|
+
except Exception: # the link is a convenience: the API path below still works
|
|
68
|
+
signed = None
|
|
69
|
+
if signed:
|
|
70
|
+
return f'curl -fL "{signed}" -o {fname}'
|
|
71
|
+
url = f"{client.base_url.rstrip('/')}{d['url']}"
|
|
72
|
+
return f'curl -fL -H "Authorization: Bearer $KITE_API_KEY" "{url}" -o {fname}'
|
|
73
|
+
return f'curl -fL "{d["url"]}" -o {fname}'
|
|
74
|
+
|
|
75
|
+
|
|
59
76
|
def _twin_view(twin: dict, client: KiteClient) -> dict:
|
|
60
77
|
"""A twin as an agent should see it: the resource plus `next`, the one thing to do now. A hosted MCP server
|
|
61
78
|
cannot write to the agent's disk, so a finished twin's `next` is the exact command that fetches and
|
|
@@ -65,11 +82,7 @@ def _twin_view(twin: dict, client: KiteClient) -> dict:
|
|
|
65
82
|
d = twin.get("download") or {}
|
|
66
83
|
if status == "succeeded" and d:
|
|
67
84
|
fname = d.get("filename") or f"{tid}.tar.gz"
|
|
68
|
-
|
|
69
|
-
url = f"{client.base_url.rstrip('/')}{d['url']}"
|
|
70
|
-
fetch = f'curl -fL -H "Authorization: Bearer $KITE_API_KEY" "{url}" -o {fname}'
|
|
71
|
-
else:
|
|
72
|
-
fetch = f'curl -fL "{d["url"]}" -o {fname}'
|
|
85
|
+
fetch = _fetch_command(d, fname, client)
|
|
73
86
|
out["next"] = (f"{fetch} && tar xzf {fname} # -> {d.get('scene')}"
|
|
74
87
|
+ (f" (link valid until {d['expires_at']}; call kite_twin_status again for a fresh one)"
|
|
75
88
|
if d.get("expires_at") else ""))
|
|
@@ -95,11 +108,7 @@ def _eval_view(run: dict, client: KiteClient) -> dict:
|
|
|
95
108
|
d = run.get("download") or {}
|
|
96
109
|
if status == "succeeded" and d:
|
|
97
110
|
fname = d.get("filename") or f"{rid}.tar.gz"
|
|
98
|
-
|
|
99
|
-
url = f"{client.base_url.rstrip('/')}{d['url']}"
|
|
100
|
-
fetch = f'curl -fL -H "Authorization: Bearer $KITE_API_KEY" "{url}" -o {fname}'
|
|
101
|
-
else:
|
|
102
|
-
fetch = f'curl -fL "{d["url"]}" -o {fname}'
|
|
111
|
+
fetch = _fetch_command(d, fname, client)
|
|
103
112
|
out["next"] = (f"{fetch} && tar xzf {fname} # -> {rid}/, the run directory (`kite verify {rid} --evals <the spec's evaluators>` checks it)"
|
|
104
113
|
+ (f" (link valid until {d['expires_at']}; call kite_eval_status again for a fresh one)"
|
|
105
114
|
if d.get("expires_at") else "") + f"; kite_eval_report {rid} has the scorecard")
|
|
@@ -131,10 +140,11 @@ def _eval_request(args: dict, client: KiteClient) -> tuple[dict, dict | None]:
|
|
|
131
140
|
raise ValueError("`spec` only works with the local (stdio) Kite MCP server; send `spec_yaml` and `files`")
|
|
132
141
|
from pathlib import Path
|
|
133
142
|
|
|
134
|
-
from kite_cli.cli.commands.evaluations import hf_refs, request
|
|
143
|
+
from kite_cli.cli.commands.evaluations import hf_refs, request, s3_refs
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
remote = {**hf_refs([f"{k}={v}" for k, v in (args.get("hf") or {}).items()]),
|
|
146
|
+
**s3_refs([f"{k}={v}" for k, v in (args.get("s3") or {}).items()])}
|
|
147
|
+
body, validation, _ = request(client, Path(args["spec"]), args.get("overrides") or [], remote, args.get("kite_version"))
|
|
138
148
|
return body, validation
|
|
139
149
|
|
|
140
150
|
|
|
@@ -194,9 +204,10 @@ _EVAL_SPEC = {
|
|
|
194
204
|
"spec": {"type": "string", "description": "A Kite SDK spec file on this machine, best as an absolute path (the local server only): every path it names is sent from this disk, a file Kite ships by its hash alone"},
|
|
195
205
|
"spec_yaml": {"type": "string", "description": "The spec's text, instead of `spec` (the hosted server): send `spec_path` and `files` with it"},
|
|
196
206
|
"spec_path": {"type": "string", "description": "With spec_yaml: its path in your workspace, e.g. specs/smoke.yaml. Paths in the spec are read relative to it"},
|
|
197
|
-
"files": {"type": "object", "description": "With spec_yaml: each path the spec names, as it names it, to where its bytes come from: {\"kind\": \"directory\", \"shipped\": \"evals\"} (a path Kite ships), {\"sha256\", \"kind\"} (a shipped file by hash), {\"text\"} or {\"base64\"} (a folder as a .tar), or {\"hf\": {\"repo\", \"revision\" (a full commit), \"path\"}}. A missing one is files_required, listing each"},
|
|
207
|
+
"files": {"type": "object", "description": "With spec_yaml: each path the spec names, as it names it, to where its bytes come from: {\"kind\": \"directory\", \"shipped\": \"evals\"} (a path Kite ships), {\"sha256\", \"kind\"} (a shipped file by hash), {\"text\"} or {\"base64\"} (a folder as a .tar), or {\"hf\": {\"repo\", \"revision\" (a full commit), \"path\"}} or {\"hf\": \"<the file's huggingface.co URL>\"} (a branch is pinned to its commit), or {\"s3\": \"s3://BUCKET/KEY\"} (an object in a public bucket, pinned to its ETag). A missing one is files_required, listing each"},
|
|
198
208
|
"overrides": {"type": "array", "items": {"type": "string"}, "description": "FIELD=VALUE pairs, as `kite run --set`, e.g. episodes.count=20. Optional"},
|
|
199
|
-
"hf": {"type": "object", "description": "With spec: {PATH: \"OWNER/NAME@COMMIT:FILE\"}, the paths that are a public Hugging Face file (a checkpoint's weights). Optional"},
|
|
209
|
+
"hf": {"type": "object", "description": "With spec: {PATH: \"OWNER/NAME@COMMIT:FILE\" or the file's huggingface.co URL}, the paths that are a public Hugging Face file (a checkpoint's weights). Optional"},
|
|
210
|
+
"s3": {"type": "object", "description": "With spec: {PATH: \"s3://BUCKET/KEY\"}, the paths that are an object in a public S3 bucket. Optional"},
|
|
200
211
|
"kite_version": {"type": "string", "description": "The Kite release you expect; refused if it is not the one served. Optional"},
|
|
201
212
|
}
|
|
202
213
|
_EVAL_ID = {"run_id": {"type": "string", "description": "Evaluation id (eval_...)"}}
|
|
@@ -208,9 +219,9 @@ _EVAL_ID = {"run_id": {"type": "string", "description": "Evaluation id (eval_...
|
|
|
208
219
|
TOOLS = [
|
|
209
220
|
# Augmentations (public /v1 API — relighting + video augmentation)
|
|
210
221
|
Tool(name="kite_augment_create", title="Create Augmentation",
|
|
211
|
-
description="Create a dataset augmentation run from a HuggingFace
|
|
222
|
+
description="Create a dataset augmentation run from a LeRobot dataset on HuggingFace or in a public S3 bucket. model 'auto' (default) lets a planner pick per camera: 'relight' (cheap, exact colour/exposure/warmth transform of every episode — e.g. match a darker, warmer deployment scene), 'background' (a new place behind the work cell from a panorama; the robot, objects and table keep their recorded pixels), 'world' (a 3D world rendered from each camera's pose, for moving cameras) or 'video' (generative video augmentation of content: textures, objects). background and world run on whole datasets and may not be enabled for every account. Pass reference_images of the target look. Returns the augmentation resource with its id (aug_...) and, once planned, the chosen plan.",
|
|
212
223
|
inputSchema={"type": "object", "properties": {
|
|
213
|
-
"repo_id": {"type": "string", "description": "Source
|
|
224
|
+
"repo_id": {"type": "string", "description": "Source LeRobot dataset: a HuggingFace id (lerobot/pusht) or its huggingface.co/datasets/ URL, or an S3 link to its folder in a public bucket (s3://bucket/dataset/ or its https://….amazonaws.com URL)"},
|
|
214
225
|
"instructions": {"type": "string", "description": "Augmentation instruction"},
|
|
215
226
|
"episode_count": {"type": "integer", "description": "Video: episodes to generate (1-50, default 3)"},
|
|
216
227
|
"episodes": {"anyOf": [{"type": "integer", "minimum": 1}, {"type": "string", "enum": ["all"]}],
|
|
@@ -228,6 +239,7 @@ TOOLS = [
|
|
|
228
239
|
"variants": {"type": "integer", "description": "background/world: environments per source episode (default 1)"},
|
|
229
240
|
"preview_first": {"type": "boolean", "description": "background/world: check one episode's quality gates (windows at its start, middle and end) before charging for the rest (default true)"},
|
|
230
241
|
"allow_video_fallback": {"type": "boolean", "description": "auto: where no scene engine can run, use generative video (far more tokens, no pixel guarantee) instead of failing with the reasons and the video price (default false)"},
|
|
242
|
+
"video_check": {"type": "string", "enum": ["standard", "strict", "off"], "description": "Generative video: what the check of each clip rejects. standard (default): a changed robot or a missing, duplicated or swapped object; an object redrawn or the table or view redrawn ship as warnings. strict: every problem rejects. off: records warnings, never rejects"},
|
|
231
243
|
"reference_images": {"type": "array", "maxItems": 6, "description": "Photos of the target look", "items": {
|
|
232
244
|
"type": "object", "properties": {
|
|
233
245
|
"camera": {"type": "string", "description": "Camera it shows (e.g. 'top'); omit for all cameras"},
|
|
@@ -419,7 +431,8 @@ async def _dispatch(name: str, args: dict, client: KiteClient) -> list[TextConte
|
|
|
419
431
|
"source": {"type": "huggingface", "repo_id": args["repo_id"]},
|
|
420
432
|
"instructions": args["instructions"],
|
|
421
433
|
"config": scene_config(args.get("episodes", args.get("episode_count")), args.get("model"),
|
|
422
|
-
args.get("variants"), args.get("preview_first"), args.get("allow_video_fallback")
|
|
434
|
+
args.get("variants"), args.get("preview_first"), args.get("allow_video_fallback"),
|
|
435
|
+
args.get("video_check")),
|
|
423
436
|
"output": {"type": args.get("output_type", "download")},
|
|
424
437
|
**{k: args[k] for k in ("environments", "must_keep") if args.get(k)},
|
|
425
438
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|