kiteml-cli 0.3.1__tar.gz → 0.4.1__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 (23) hide show
  1. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/PKG-INFO +10 -8
  2. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/README.md +9 -7
  3. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/__init__.py +1 -1
  4. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/archive.py +1 -1
  5. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/augment.py +82 -5
  6. kiteml_cli-0.3.1/src/kite_cli/cli/commands/sim.py → kiteml_cli-0.4.1/src/kite_cli/cli/commands/evaluations.py +51 -49
  7. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/main.py +4 -4
  8. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/client.py +11 -0
  9. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/mcp/server.py +95 -69
  10. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/.gitignore +0 -0
  11. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/pyproject.toml +0 -0
  12. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/auth.py +0 -0
  13. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/__init__.py +0 -0
  14. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/__init__.py +0 -0
  15. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/auth_cmd.py +0 -0
  16. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/config_cmd.py +0 -0
  17. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/rl.py +0 -0
  18. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/twin.py +0 -0
  19. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/config.py +0 -0
  20. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/mcp/__init__.py +0 -0
  21. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/mcp/common.py +0 -0
  22. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/output.py +0 -0
  23. {kiteml_cli-0.3.1 → kiteml_cli-0.4.1}/src/kite_cli/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kiteml-cli
3
- Version: 0.3.1
3
+ Version: 0.4.1
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
6
  Project-URL: Documentation, https://kite-ml.mintlify.app/tools/cli-and-mcp
@@ -59,8 +59,9 @@ Model Context Protocol (MCP) client.
59
59
  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
- - **Sim runs (beta).** Run a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU policy: every episode traced, every
63
- evaluator scored. You get a scorecard and the run directory, which `kite verify` checks.
62
+ - **Evaluations (beta).** Evaluate a policy by running a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU
63
+ policy: every episode traced, every evaluator scored. You get a scorecard and the run directory, which
64
+ `kite verify` checks.
64
65
 
65
66
  ## Install
66
67
 
@@ -108,7 +109,7 @@ lerobot/pusht to look like this photo".
108
109
  | `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's evaluation verdict |
109
110
  | `kite_augment_create`, `kite_augment_status`, `kite_augment_list`, `kite_augment_cancel` | Augment LeRobot datasets |
110
111
  | `kite_twin_validate`, `kite_twin_create`, `kite_twin_status`, `kite_twin_list`, `kite_twin_cancel`, `kite_twin_resume` | Build MuJoCo digital twins (beta) |
111
- | `kite_sim_validate`, `kite_sim_run`, `kite_sim_status`, `kite_sim_list`, `kite_sim_report`, `kite_sim_logs`, `kite_sim_cancel`, `kite_sim_resume` | Run Kite SDK specs on Kite's machines and read their scorecards (beta) |
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) |
112
113
  | `kite_doctor` | Check connectivity and authentication |
113
114
 
114
115
  ## Use it from the terminal
@@ -137,11 +138,11 @@ Build a digital twin (beta, enabled per account):
137
138
  kite twin create lerobot/svla_so101_pickplace --out ./twins # -> ./twins/twin_.../scene.xml
138
139
  ```
139
140
 
140
- Run a Kite SDK spec on Kite's machines (beta):
141
+ Evaluate a policy on a Kite SDK spec, on Kite's machines (beta):
141
142
 
142
143
  ```bash
143
- kite sim validate specs/smoke.yaml # free: the plan and what a run reserves
144
- kite sim run specs/smoke.yaml --out ./runs # -> ./runs/srun_.../scorecard.json
144
+ kite eval validate specs/smoke.yaml # free: the plan and what a run reserves
145
+ kite eval run specs/smoke.yaml --out ./runs # -> ./runs/eval_.../scorecard.json
145
146
  ```
146
147
 
147
148
  Every command prints JSON (`{"ok": true, "data": ...}`) so scripts and agents can parse it. Run `kite --help` for
@@ -152,7 +153,8 @@ the full list.
152
153
  - Docs: [CLI and MCP](https://kite-ml.mintlify.app/tools/cli-and-mcp) ·
153
154
  [RL runs](https://kite-ml.mintlify.app/platform-api/rl-runs) ·
154
155
  [Augmentations](https://kite-ml.mintlify.app/platform-api/augmentation) ·
155
- [Twins](https://kite-ml.mintlify.app/platform-api/twins)
156
+ [Twins](https://kite-ml.mintlify.app/platform-api/twins) ·
157
+ [Evaluations](https://kite-ml.mintlify.app/platform-api/evaluations)
156
158
  - Dashboard: [app.kiteml.com](https://app.kiteml.com)
157
159
  - Website: [kiteml.com](https://kiteml.com)
158
160
  - Support: [raul@kiteml.com](mailto:raul@kiteml.com)
@@ -17,8 +17,9 @@ Model Context Protocol (MCP) client.
17
17
  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
- - **Sim runs (beta).** Run a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU policy: every episode traced, every
21
- evaluator scored. You get a scorecard and the run directory, which `kite verify` checks.
20
+ - **Evaluations (beta).** Evaluate a policy by running a Kite SDK spec on Kite's machines, on an NVIDIA L4 for a GPU
21
+ policy: every episode traced, every evaluator scored. You get a scorecard and the run directory, which
22
+ `kite verify` checks.
22
23
 
23
24
  ## Install
24
25
 
@@ -66,7 +67,7 @@ lerobot/pusht to look like this photo".
66
67
  | `kite_rl_status`, `kite_rl_metrics`, `kite_rl_report`, `kite_rl_list` | Follow training and read each run's evaluation verdict |
67
68
  | `kite_augment_create`, `kite_augment_status`, `kite_augment_list`, `kite_augment_cancel` | Augment LeRobot datasets |
68
69
  | `kite_twin_validate`, `kite_twin_create`, `kite_twin_status`, `kite_twin_list`, `kite_twin_cancel`, `kite_twin_resume` | Build MuJoCo digital twins (beta) |
69
- | `kite_sim_validate`, `kite_sim_run`, `kite_sim_status`, `kite_sim_list`, `kite_sim_report`, `kite_sim_logs`, `kite_sim_cancel`, `kite_sim_resume` | Run Kite SDK specs on Kite's machines and read their scorecards (beta) |
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) |
70
71
  | `kite_doctor` | Check connectivity and authentication |
71
72
 
72
73
  ## Use it from the terminal
@@ -95,11 +96,11 @@ Build a digital twin (beta, enabled per account):
95
96
  kite twin create lerobot/svla_so101_pickplace --out ./twins # -> ./twins/twin_.../scene.xml
96
97
  ```
97
98
 
98
- Run a Kite SDK spec on Kite's machines (beta):
99
+ Evaluate a policy on a Kite SDK spec, on Kite's machines (beta):
99
100
 
100
101
  ```bash
101
- kite sim validate specs/smoke.yaml # free: the plan and what a run reserves
102
- kite sim run specs/smoke.yaml --out ./runs # -> ./runs/srun_.../scorecard.json
102
+ kite eval validate specs/smoke.yaml # free: the plan and what a run reserves
103
+ kite eval run specs/smoke.yaml --out ./runs # -> ./runs/eval_.../scorecard.json
103
104
  ```
104
105
 
105
106
  Every command prints JSON (`{"ok": true, "data": ...}`) so scripts and agents can parse it. Run `kite --help` for
@@ -110,7 +111,8 @@ the full list.
110
111
  - Docs: [CLI and MCP](https://kite-ml.mintlify.app/tools/cli-and-mcp) ·
111
112
  [RL runs](https://kite-ml.mintlify.app/platform-api/rl-runs) ·
112
113
  [Augmentations](https://kite-ml.mintlify.app/platform-api/augmentation) ·
113
- [Twins](https://kite-ml.mintlify.app/platform-api/twins)
114
+ [Twins](https://kite-ml.mintlify.app/platform-api/twins) ·
115
+ [Evaluations](https://kite-ml.mintlify.app/platform-api/evaluations)
114
116
  - Dashboard: [app.kiteml.com](https://app.kiteml.com)
115
117
  - Website: [kiteml.com](https://kiteml.com)
116
118
  - Support: [raul@kiteml.com](mailto:raul@kiteml.com)
@@ -1,3 +1,3 @@
1
1
  """Kite CLI and MCP server for the Kite robotics training platform."""
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.4.1"
@@ -1,4 +1,4 @@
1
- """Fetching a finished resource's `.tar.gz` (a twin, a sim run): to `<out>/<id>.tar.gz`, sha256-verified, and unpacked
1
+ """Fetching a finished resource's `.tar.gz` (a twin, an evaluation): to `<out>/<id>.tar.gz`, sha256-verified, and unpacked
2
2
  only inside `<out>/<id>/`.
3
3
 
4
4
  A signed storage URL is fetched with a plain HTTP client, never with the Kite key; the API's own archive route
@@ -1,7 +1,8 @@
1
1
  """`kite augment` — dataset augmentation jobs via the public /v1 API.
2
2
 
3
- create / status / download / cancel / list. Authenticates with KITE_API_KEY like the
4
- rest of the CLI; calls the bare `/v1/augmentations` surface.
3
+ create / status / download / cancel / list, and `environments list` (what the background and
4
+ world engines put behind the work cell). Authenticates with KITE_API_KEY like the rest of the
5
+ CLI; calls the bare `/v1/augmentations` surface.
5
6
  """
6
7
 
7
8
  from __future__ import annotations
@@ -18,6 +19,9 @@ from kite_cli.output import print_error, print_json
18
19
  from kite_cli.progress import wait_for_completion
19
20
 
20
21
  app = typer.Typer(no_args_is_help=True, help="Dataset augmentation jobs")
22
+ environments_app = typer.Typer(no_args_is_help=True, help="Environments the background and world engines put "
23
+ "behind the work cell")
24
+ app.add_typer(environments_app, name="environments")
21
25
 
22
26
  _TERMINAL = {"succeeded", "failed", "canceled"}
23
27
  _MEDIA_TYPES = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp"}
@@ -33,13 +37,64 @@ def reference_image(path: str, camera: Optional[str] = None) -> dict:
33
37
  return {"camera": camera, **entry} if camera else entry
34
38
 
35
39
 
40
+ def scene_config(episodes, model: Optional[str], variants: Optional[int] = None,
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)."""
46
+ config: dict = {}
47
+ if episodes is not None:
48
+ if str(episodes) != "all" and not str(episodes).isdigit():
49
+ raise typer.BadParameter("a count or 'all'", param_hint="--episodes")
50
+ if model in ("background", "world"):
51
+ config["episodes"] = "all" if str(episodes) == "all" else int(episodes)
52
+ elif str(episodes) != "all":
53
+ config["episode_count"] = int(episodes)
54
+ if variants is not None:
55
+ config["variants"] = variants
56
+ if preview is not None:
57
+ config["preview_first"] = preview
58
+ if allow_video_fallback:
59
+ config["allow_video_fallback"] = True
60
+ if video_check:
61
+ config["video_check"] = video_check
62
+ return config
63
+
64
+
36
65
  @app.command()
37
66
  def create(
38
67
  repo_id: str = typer.Option(..., "--repo-id", help="Source HF dataset id, e.g. lerobot/pusht"),
39
68
  instructions: str = typer.Option(..., "-i", "--instructions", help="Augmentation instruction"),
40
- episodes: int = typer.Option(3, "-n", "--episodes", help="Episodes to generate with video augmentation (1-50); relight covers every episode"),
69
+ episodes: Optional[str] = typer.Option(
70
+ None, "-n", "--episodes",
71
+ help="A count (video: 1-50, default 3) or 'all' (background/world, their default); relight covers every "
72
+ "episode",
73
+ ),
41
74
  model: Optional[str] = typer.Option(
42
- None, "--model", help="auto (default: pick relight or video) | relight (colour/exposure, every episode) | video (generative)"
75
+ None, "--model",
76
+ help="auto (default: the planner picks per camera) | relight (colour/exposure) | background (a panorama behind "
77
+ "the work cell) | world (a 3D world, for moving cameras) | video (generative)",
78
+ ),
79
+ environments: Optional[str] = typer.Option(
80
+ None, "--environments", help="Background/world: comma-separated environment ids (aenv_...) or library ids"
81
+ ),
82
+ env_count: Optional[int] = typer.Option(None, "--env-count", help="Background/world: environments to pick for the rig"),
83
+ must_keep: Optional[List[str]] = typer.Option(
84
+ None, "--must-keep", help="Something that must keep its recorded pixels (repeatable)"
85
+ ),
86
+ variants: Optional[int] = typer.Option(None, "--variants", help="Background/world: environments per source episode"),
87
+ preview: Optional[bool] = typer.Option(
88
+ None, "--preview/--no-preview", help="Background/world: check one episode's quality gates before the rest"
89
+ ),
90
+ allow_video_fallback: bool = typer.Option(
91
+ False, "--allow-video-fallback",
92
+ help="auto: use generative video (far more tokens, no pixel guarantee) where no scene engine can run, "
93
+ "instead of failing",
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)",
43
98
  ),
44
99
  reference_images: Optional[List[str]] = typer.Option(
45
100
  None, "--reference-image", help="Photo of the target look, [camera=]PATH (repeatable)"
@@ -59,13 +114,18 @@ def create(
59
114
  body: dict = {
60
115
  "source": {"type": "huggingface", "repo_id": repo_id},
61
116
  "instructions": instructions,
62
- "config": {"episode_count": episodes},
117
+ "config": scene_config(episodes, model, variants, preview, allow_video_fallback, video_check),
63
118
  "output": {"type": output, "private": private},
64
119
  }
65
120
  if hf_repo:
66
121
  body["output"]["repo_id"] = hf_repo
67
122
  if model:
68
123
  body["model"] = model
124
+ if environments or env_count:
125
+ body["environments"] = {k: v for k, v in (
126
+ ("ids", [e.strip() for e in (environments or "").split(",") if e.strip()]), ("count", env_count)) if v}
127
+ if must_keep:
128
+ body["must_keep"] = list(must_keep)
69
129
  try:
70
130
  if relight:
71
131
  body["config"]["relight"] = json.loads(relight)
@@ -135,6 +195,23 @@ def cancel(aug_id: str = typer.Argument(..., help="Augmentation id (aug_...)")):
135
195
  print_error(e.message, code=e.code, details=e.details)
136
196
 
137
197
 
198
+ @environments_app.command("list")
199
+ def list_environments(
200
+ owner: str = typer.Option("user", "--owner", help="user (yours) or kite (the Kite library)"),
201
+ kind: Optional[str] = typer.Option(None, "--kind", help="panorama or world"),
202
+ category: Optional[str] = typer.Option(None, "--category", help="workshop, warehouse, hangar, lab, ..."),
203
+ lighting: Optional[str] = typer.Option(None, "--lighting", help="daylight or artificial"),
204
+ limit: int = typer.Option(20, "--limit", help="Max rows (1-100)"),
205
+ ):
206
+ """List environments: yours, or the Kite library's (--owner kite)."""
207
+ params = {"owner": owner, "kind": kind, "category": category, "lighting": lighting, "limit": limit}
208
+ try:
209
+ client = KiteClient()
210
+ print_json(client.get("/v1/augmentations/environments", params={k: v for k, v in params.items() if v}))
211
+ except KiteAPIError as e:
212
+ print_error(e.message, code=e.code, details=e.details)
213
+
214
+
138
215
  @app.command()
139
216
  def download(
140
217
  aug_id: str = typer.Argument(..., help="Augmentation id (aug_...)"),
@@ -1,22 +1,23 @@
1
- """`kite sim` — a Kite SDK spec run on Kite's machines via the public /v1/sim_runs API: the spec in, a scorecard and a
2
- verifiable run directory out. The policy runs on an NVIDIA L4 if its device is `cuda`, on a CPU machine otherwise.
3
-
4
- kite sim validate specs/smoke.yaml [--set episodes.count=20] free: the plan, and what a run reserves
5
- kite sim run specs/smoke.yaml --wait --out ./runs -> ./runs/srun_.../scorecard.json
6
- kite sim status srun_01K... [--wait]
7
- kite sim list [--limit 20] [--after srun_...]
8
- kite sim report srun_01K... the scorecard
9
- kite sim logs srun_01K... [--tail 200]
10
- kite sim files srun_01K... [--limit 100] [--after PATH] [--all]
11
- kite sim download srun_01K... [--out DIR] [--no-extract]
12
- kite sim cancel srun_01K... -> canceling, then canceled once its job has stopped
13
- kite sim resume srun_01K...
1
+ """`kite eval` — an evaluation: a Kite SDK spec run on Kite's machines via the public /v1/evaluations API, the spec
2
+ in, a scorecard and a verifiable run directory out. The policy runs on an NVIDIA L4 if its device is `cuda`, on a CPU
3
+ machine otherwise. (Until 2026-10-18 this was `kite sim`, over /v1/sim_runs.)
4
+
5
+ kite eval validate specs/smoke.yaml [--set episodes.count=20] free: the plan, and what a run reserves
6
+ kite eval run specs/smoke.yaml --wait --out ./runs -> ./runs/eval_.../scorecard.json
7
+ kite eval status eval_01K... [--wait]
8
+ kite eval list [--limit 20] [--after eval_...]
9
+ kite eval report eval_01K... the scorecard
10
+ kite eval logs eval_01K... [--tail 200]
11
+ kite eval files eval_01K... [--limit 100] [--after PATH] [--all]
12
+ kite eval download eval_01K... [--out DIR] [--no-extract]
13
+ kite eval cancel eval_01K... -> canceling, then canceled once its job has stopped
14
+ kite eval resume eval_01K...
14
15
 
15
16
  SPEC is the file `kite run` takes (kite_spec_version "2"). The command sends its text, asks the API which paths it
16
17
  names (`files_required`), and sends each from this disk: a file or folder Kite ships by its sha256 alone, anything
17
18
  else inline (about 15 MB in all). A file on the Hugging Face Hub, such as a checkpoint's weights, goes as a
18
19
  reference: `--hf PATH=OWNER/NAME@COMMIT:FILE`, PATH as the spec names it (a folder's file as FOLDER/FILE).
19
- Where runs go: --out DIR, else $KITE_SIM_RUNS_DIR, else ./runs (`~` works). Paths printed are absolute. A run
20
+ Where runs go: --out DIR, else $KITE_EVAL_RUNS_DIR, else ./runs (`~` works). Paths printed are absolute. A run
20
21
  directory extracts to <out>/<run_id>/; the SDK's `kite verify <out>/<run_id> --evals DIR` checks it, DIR being the
21
22
  evaluators the spec named (the run records where its job read them, not your folder).
22
23
  """
@@ -42,7 +43,7 @@ from kite_cli.client import KiteAPIError, KiteClient, post_create
42
43
  from kite_cli.output import print_error, print_json
43
44
  from kite_cli.progress import wait_for_completion
44
45
 
45
- app = typer.Typer(no_args_is_help=True, help="Sim runs: a Kite SDK spec on Kite's machines -> a scorecard and a "
46
+ app = typer.Typer(no_args_is_help=True, help="Evaluations: a Kite SDK spec on Kite's machines -> a scorecard and a "
46
47
  "verifiable run directory")
47
48
 
48
49
  _TERMINAL = {"succeeded", "failed", "canceled"} # not canceling: the cancel was accepted, the job has not stopped
@@ -104,37 +105,37 @@ def run(
104
105
  ("verify_determinism", verify_determinism)) if v}
105
106
  body = {**body, **({"options": options} if options else {}), **({"label": label} if label else {})}
106
107
  try:
107
- srun = post_create(client, "/v1/sim_runs", body, key)
108
+ run = post_create(client, "/v1/evaluations", body, key)
108
109
  except (httpx.TimeoutException, httpx.NetworkError) as e:
109
110
  print_error(f"network error ({e}); re-run with --idempotency-key {key} to retry without running twice",
110
111
  code="network_error", details={"idempotency_key": key})
111
112
  return
112
- if not (wait or out) or not srun.get("id"):
113
- print_json(srun)
113
+ if not (wait or out) or not run.get("id"):
114
+ print_json(run)
114
115
  return
115
- srun = wait_for_completion(client, f"/v1/sim_runs/{srun['id']}", terminal_states=_TERMINAL,
116
- poll_interval=_POLL_S)
117
- if srun.get("status") != "succeeded":
118
- print_json(srun, success=False)
116
+ run = wait_for_completion(client, f"/v1/evaluations/{run['id']}", terminal_states=_TERMINAL,
117
+ poll_interval=_POLL_S)
118
+ if run.get("status") != "succeeded":
119
+ print_json(run, success=False)
119
120
  raise typer.Exit(1)
120
121
  if out is None:
121
- print_json(srun)
122
+ print_json(run)
122
123
  return
123
- saved = _save(client, srun, _out_dir(out), extract=True)
124
- print_json({"ok": True, "data": srun, "run_dir": saved["run_dir"], "archive": saved["archive"]})
124
+ saved = _save(client, run, _out_dir(out), extract=True)
125
+ print_json({"ok": True, "data": run, "run_dir": saved["run_dir"], "archive": saved["archive"]})
125
126
  except KiteAPIError as e:
126
127
  print_error(e.message, code=e.code, details=e.details)
127
128
 
128
129
 
129
130
  @app.command()
130
131
  def status(
131
- run_id: str = typer.Argument(..., help="Sim run id (srun_...)"),
132
+ run_id: str = typer.Argument(..., help="Evaluation id (eval_...)"),
132
133
  wait: bool = typer.Option(False, "--wait", help="Poll until succeeded | failed | canceled"),
133
134
  ):
134
135
  """A run's live status, phase, episodes done, and once it has succeeded its result and `download`."""
135
136
  try:
136
137
  client = KiteClient()
137
- path = f"/v1/sim_runs/{run_id}"
138
+ path = f"/v1/evaluations/{run_id}"
138
139
  if wait:
139
140
  print_json(wait_for_completion(client, path, terminal_states=_TERMINAL, poll_interval=_POLL_S))
140
141
  else:
@@ -148,43 +149,43 @@ def list_runs(
148
149
  limit: int = typer.Option(20, "--limit", help="Max rows (1-100)"),
149
150
  after: Optional[str] = typer.Option(None, "--after", help="Cursor: the previous page's next_cursor"),
150
151
  ):
151
- """List your sim runs (newest first, last status seen; `status` has live progress)."""
152
+ """List your evaluations (newest first, last status seen; `status` has live progress)."""
152
153
  try:
153
- print_json(KiteClient().get("/v1/sim_runs", params=_page(limit, after)))
154
+ print_json(KiteClient().get("/v1/evaluations", params=_page(limit, after)))
154
155
  except KiteAPIError as e:
155
156
  print_error(e.message, code=e.code, details=e.details)
156
157
 
157
158
 
158
159
  @app.command()
159
- def report(run_id: str = typer.Argument(..., help="Sim run id (srun_...)")):
160
+ def report(run_id: str = typer.Argument(..., help="Evaluation id (eval_...)")):
160
161
  """A succeeded run's scorecard: every evaluator's value, interval and count, and what was not measured."""
161
162
  try:
162
- print_json(KiteClient().get(f"/v1/sim_runs/{run_id}/report"))
163
+ print_json(KiteClient().get(f"/v1/evaluations/{run_id}/report"))
163
164
  except KiteAPIError as e:
164
165
  print_error(e.message, code=e.code, details=e.details)
165
166
 
166
167
 
167
168
  @app.command()
168
169
  def logs(
169
- run_id: str = typer.Argument(..., help="Sim run id (srun_...)"),
170
+ run_id: str = typer.Argument(..., help="Evaluation id (eval_...)"),
170
171
  tail: int = typer.Option(200, "--tail", help="The last N lines"),
171
172
  ):
172
173
  """What the SDK printed, oldest first: readable while the run builds and after."""
173
174
  try:
174
- print_json(KiteClient().get(f"/v1/sim_runs/{run_id}/logs", params={"tail": tail}))
175
+ print_json(KiteClient().get(f"/v1/evaluations/{run_id}/logs", params={"tail": tail}))
175
176
  except KiteAPIError as e:
176
177
  print_error(e.message, code=e.code, details=e.details)
177
178
 
178
179
 
179
180
  @app.command("files")
180
181
  def list_files(
181
- run_id: str = typer.Argument(..., help="Sim run id (srun_...)"),
182
+ run_id: str = typer.Argument(..., help="Evaluation id (eval_...)"),
182
183
  limit: Optional[int] = typer.Option(None, "--limit", help="Files per page, 1-100 (default 100)"),
183
184
  after: Optional[str] = typer.Option(None, "--after", help="Cursor: the previous page's next_cursor (a file path)"),
184
185
  all_pages: bool = typer.Option(False, "--all", help="Follow next_cursor through every page; print one list"),
185
186
  ):
186
187
  """The files of a succeeded run's directory (path, bytes, sha256), sorted by path."""
187
- path = f"/v1/sim_runs/{run_id}/files"
188
+ path = f"/v1/evaluations/{run_id}/files"
188
189
  size = limit or 100
189
190
  try:
190
191
  client = KiteClient()
@@ -206,8 +207,8 @@ def list_files(
206
207
 
207
208
  @app.command()
208
209
  def download(
209
- run_id: str = typer.Argument(..., help="Sim run id (srun_...)"),
210
- out: Optional[Path] = typer.Option(None, "-o", "--out", help="Output directory (default: $KITE_SIM_RUNS_DIR, "
210
+ run_id: str = typer.Argument(..., help="Evaluation id (eval_...)"),
211
+ out: Optional[Path] = typer.Option(None, "-o", "--out", help="Output directory (default: $KITE_EVAL_RUNS_DIR, "
211
212
  "else ./runs)"),
212
213
  extract: bool = typer.Option(True, "--extract/--no-extract", help="Unpack into <out>/<run_id>/, or keep only "
213
214
  "the .tar.gz"),
@@ -215,26 +216,26 @@ def download(
215
216
  """Download a succeeded run: <out>/<run_id>.tar.gz, sha256-verified, unpacked to <out>/<run_id>/."""
216
217
  try:
217
218
  client = KiteClient(timeout=120)
218
- print_json(_save(client, client.get(f"/v1/sim_runs/{run_id}"), _out_dir(out), extract=extract))
219
+ print_json(_save(client, client.get(f"/v1/evaluations/{run_id}"), _out_dir(out), extract=extract))
219
220
  except KiteAPIError as e:
220
221
  print_error(e.message, code=e.code, details=e.details)
221
222
 
222
223
 
223
224
  @app.command()
224
- def cancel(run_id: str = typer.Argument(..., help="Sim run id (srun_...)")):
225
+ def cancel(run_id: str = typer.Argument(..., help="Evaluation id (eval_...)")):
225
226
  """Stop a queued or running run: canceling until its job has stopped, then canceled. It pays the minutes it
226
227
  ran."""
227
228
  try:
228
- print_json(KiteClient().post(f"/v1/sim_runs/{run_id}/cancel"))
229
+ print_json(KiteClient().post(f"/v1/evaluations/{run_id}/cancel"))
229
230
  except KiteAPIError as e:
230
231
  print_error(e.message, code=e.code, details=e.details)
231
232
 
232
233
 
233
234
  @app.command()
234
- def resume(run_id: str = typer.Argument(..., help="Sim run id (srun_...)")):
235
+ def resume(run_id: str = typer.Argument(..., help="Evaluation id (eval_...)")):
235
236
  """Run a failed or canceled run again from its request, as a new attempt: it reserves its cap again."""
236
237
  try:
237
- print_json(KiteClient().post(f"/v1/sim_runs/{run_id}/resume"))
238
+ print_json(KiteClient().post(f"/v1/evaluations/{run_id}/resume"))
238
239
  except KiteAPIError as e:
239
240
  print_error(e.message, code=e.code, details=e.details)
240
241
 
@@ -265,7 +266,7 @@ def request(client: KiteClient, spec: Path, overrides: List[str], hf: Dict[str,
265
266
  body["kite_version"] = kite_version
266
267
  for _ in range(_ROUNDS):
267
268
  try:
268
- return body, client.post("/v1/sim_runs/validate", json_data=body), sent
269
+ return body, client.post("/v1/evaluations/validate", json_data=body), sent
269
270
  except KiteAPIError as e:
270
271
  if e.code == "files_required" and e.details.get("files"):
271
272
  for item in e.details["files"]:
@@ -415,16 +416,17 @@ def _page(limit: int, after: Optional[str]) -> dict:
415
416
 
416
417
 
417
418
  def _out_dir(out: Optional[Path]) -> Path:
418
- """--out, else $KITE_SIM_RUNS_DIR, else ./runs; absolute, so what is printed says exactly where the run is."""
419
- return Path(out or os.environ.get("KITE_SIM_RUNS_DIR") or "./runs").expanduser().resolve()
419
+ """--out, else $KITE_EVAL_RUNS_DIR, else ./runs; absolute, so what is printed says exactly where the run is."""
420
+ return Path(out or os.environ.get("KITE_EVAL_RUNS_DIR") or "./runs").expanduser().resolve()
420
421
 
421
422
 
422
- def _save(client: KiteClient, srun: dict, out: Path, *, extract: bool) -> dict:
423
+ def _save(client: KiteClient, run: dict, out: Path, *, extract: bool) -> dict:
423
424
  """Fetch a succeeded run's archive to <out>/<run_id>.tar.gz, verify its sha256, and (extract) unpack the run
424
425
  directory into <out>/<run_id>/."""
425
- rid, d = str(srun.get("id") or ""), srun.get("download")
426
- if srun.get("status") != "succeeded" or not d:
427
- raise KiteAPIError(409, f"Sim run '{rid}' is {srun.get('status')}, not succeeded.", code="sim_run_not_ready")
426
+ rid, d = str(run.get("id") or ""), run.get("download")
427
+ if run.get("status") != "succeeded" or not d:
428
+ raise KiteAPIError(409, f"Evaluation '{rid}' is {run.get('status')}, not succeeded.",
429
+ code="evaluation_not_ready")
428
430
  archive = verified(client, d, out, rid, storage=_storage_client)
429
431
  saved = {"id": rid, "archive": str(archive), "bytes": archive.stat().st_size, "sha256": str(d["sha256"]).lower()}
430
432
  if extract:
@@ -5,7 +5,7 @@ Usage:
5
5
  kite auth login
6
6
  kite rl plan open_duck_mini_v2 "walk forward at a steady pace" -o walk.json
7
7
  kite augment create --repo-id lerobot/pusht -i "white marble table" --wait
8
- kite sim run specs/smoke.yaml --wait --out ./runs
8
+ kite eval run specs/smoke.yaml --wait --out ./runs
9
9
  kite doctor
10
10
  """
11
11
 
@@ -16,7 +16,7 @@ from typing import Optional
16
16
  import typer
17
17
 
18
18
  from kite_cli import __version__
19
- from kite_cli.cli.commands import augment, auth_cmd, config_cmd, rl, sim, twin
19
+ from kite_cli.cli.commands import augment, auth_cmd, config_cmd, evaluations, rl, twin
20
20
 
21
21
  app = typer.Typer(
22
22
  name="kite",
@@ -31,8 +31,8 @@ app.add_typer(config_cmd.app, name="config", help="CLI configuration")
31
31
  app.add_typer(augment.app, name="augment", help="Dataset augmentation: relighting + video augmentation of LeRobot datasets")
32
32
  app.add_typer(twin.app, name="twin", help="Digital twins (beta): one robot episode -> an interactable MuJoCo scene")
33
33
  app.add_typer(rl.app, name="rl", help="RL training in simulation: a task spec -> a trained, verified policy")
34
- app.add_typer(sim.app, name="sim", help="Sim runs (beta): a Kite SDK spec on Kite's machines -> a scorecard and a "
35
- "verifiable run directory")
34
+ app.add_typer(evaluations.app, name="eval", help="Evaluations (beta): a Kite SDK spec on Kite's machines -> a "
35
+ "scorecard and a verifiable run directory")
36
36
 
37
37
  # Kite's own install adds the dashboard's commands (projects, training, deployment, sensors); the published package
38
38
  # has only the /v1 commands above.
@@ -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
- if d.get("requires_auth"):
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 ""))
@@ -86,8 +99,8 @@ def _twin_view(twin: dict, client: KiteClient) -> dict:
86
99
  return out
87
100
 
88
101
 
89
- def _sim_view(run: dict, client: KiteClient) -> dict:
90
- """A sim run as an agent should see it: the resource plus `next`, the one thing to do now. A hosted MCP server
102
+ def _eval_view(run: dict, client: KiteClient) -> dict:
103
+ """An evaluation as an agent should see it: the resource plus `next`, the one thing to do now. A hosted MCP server
91
104
  cannot write to the agent's disk, so a finished run's `next` is the exact command that fetches and extracts its
92
105
  run directory."""
93
106
  out = dict(run)
@@ -95,32 +108,28 @@ def _sim_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
- if d.get("requires_auth"):
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
- + (f" (link valid until {d['expires_at']}; call kite_sim_status again for a fresh one)"
105
- if d.get("expires_at") else "") + f"; kite_sim_report {rid} has the scorecard")
113
+ + (f" (link valid until {d['expires_at']}; call kite_eval_status again for a fresh one)"
114
+ if d.get("expires_at") else "") + f"; kite_eval_report {rid} has the scorecard")
106
115
  elif status in ("queued", "processing"):
107
116
  eps = run.get("episodes") or {}
108
117
  where = (f" (phase {run['phase']}" + (f", {eps['done']}/{eps['total']} episodes" if eps.get("total") else "")
109
118
  + ")") if run.get("phase") else ""
110
- out["next"] = f"call kite_sim_status again in a minute{where}"
119
+ out["next"] = f"call kite_eval_status again in a minute{where}"
111
120
  elif status == "canceling":
112
- out["next"] = ("call kite_sim_status again in a few seconds: the cancel was accepted and the job is "
121
+ out["next"] = ("call kite_eval_status again in a few seconds: the cancel was accepted and the job is "
113
122
  "stopping; it then reads canceled")
114
123
  elif status in ("failed", "canceled"):
115
- out["next"] = (f"kite_sim_resume {rid} to run it again" if (run.get("error") or {}).get("resumable", True)
124
+ out["next"] = (f"kite_eval_resume {rid} to run it again" if (run.get("error") or {}).get("resumable", True)
116
125
  else "fix the spec as the error says, and run it again")
117
126
  return out
118
127
 
119
128
 
120
- def _sim_request(args: dict, client: KiteClient) -> tuple[dict, dict | None]:
121
- """The request body a kite_sim_* tool sends, and the validation that accepted it when it was worked out here:
129
+ def _eval_request(args: dict, client: KiteClient) -> tuple[dict, dict | None]:
130
+ """The request body a kite_eval_* tool sends, and the validation that accepted it when it was worked out here:
122
131
  from `spec_yaml` and `files` as given, or (the local server only) from `spec`, a spec file on this machine,
123
- whose files are sent as `kite sim run` sends them."""
132
+ whose files are sent as `kite eval run` sends them."""
124
133
  if args.get("spec_yaml"):
125
134
  keys = ("spec_yaml", "spec_path", "files", "overrides", "kite_version")
126
135
  return {k: args[k] for k in keys if args.get(k)}, None
@@ -131,7 +140,7 @@ def _sim_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.sim import hf_refs, request
143
+ from kite_cli.cli.commands.evaluations import hf_refs, request
135
144
 
136
145
  hf = hf_refs([f"{k}={v}" for k, v in (args.get("hf") or {}).items()])
137
146
  body, validation, _ = request(client, Path(args["spec"]), args.get("overrides") or [], hf, args.get("kite_version"))
@@ -190,7 +199,7 @@ _RL_SPEC = {
190
199
  }
191
200
  _RL_RUN_ID = {"run_id": {"type": "string", "description": "RL run id (rlr_...)"}}
192
201
 
193
- _SIM_SPEC = {
202
+ _EVAL_SPEC = {
194
203
  "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
204
  "spec_yaml": {"type": "string", "description": "The spec's text, instead of `spec` (the hosted server): send `spec_path` and `files` with it"},
196
205
  "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"},
@@ -199,7 +208,7 @@ _SIM_SPEC = {
199
208
  "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"},
200
209
  "kite_version": {"type": "string", "description": "The Kite release you expect; refused if it is not the one served. Optional"},
201
210
  }
202
- _SIM_RUN_ID = {"run_id": {"type": "string", "description": "Sim run id (srun_...)"}}
211
+ _EVAL_ID = {"run_id": {"type": "string", "description": "Evaluation id (eval_...)"}}
203
212
 
204
213
  # ---------------------------------------------------------------------------
205
214
  # Tool definitions — every tool has title + annotations per Claude directory
@@ -208,15 +217,27 @@ _SIM_RUN_ID = {"run_id": {"type": "string", "description": "Sim run id (srun_...
208
217
  TOOLS = [
209
218
  # Augmentations (public /v1 API — relighting + video augmentation)
210
219
  Tool(name="kite_augment_create", title="Create Augmentation",
211
- description="Create a dataset augmentation run from a HuggingFace LeRobot dataset. model 'auto' (default) lets a planner pick 'relight' (cheap, exact colour/exposure/warmth transform of every episode — e.g. match a darker, warmer deployment scene) or 'video' (generative video augmentation of content: textures, objects, backgrounds). Pass reference_images of the target look. Returns the augmentation resource with its id (aug_...) and, once planned, the chosen plan.",
220
+ description="Create a dataset augmentation run from a HuggingFace LeRobot dataset. 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
221
  inputSchema={"type": "object", "properties": {
213
222
  "repo_id": {"type": "string", "description": "Source HF dataset id, e.g. lerobot/pusht"},
214
223
  "instructions": {"type": "string", "description": "Augmentation instruction"},
215
- "episode_count": {"type": "integer", "description": "Episodes to generate (1-50, default 3)"},
224
+ "episode_count": {"type": "integer", "description": "Video: episodes to generate (1-50, default 3)"},
225
+ "episodes": {"anyOf": [{"type": "integer", "minimum": 1}, {"type": "string", "enum": ["all"]}],
226
+ "description": "model background/world: source episodes to augment, 'all' (their default) or the first N. Any other model takes a count as episode_count; 'all' there sends none (relight covers every episode)"},
216
227
  "output_type": {"type": "string", "enum": ["download", "huggingface"], "description": "Where results go (default download)"},
217
228
  "hf_repo": {"type": "string", "description": "Target HF repo id (for output_type huggingface)"},
218
229
  "variation_prompts": {"type": "array", "items": {"type": "string"}, "description": "Per-episode prompts (overrides episode_count)"},
219
- "model": {"type": "string", "enum": ["auto", "relight", "video"], "description": "Engine (default auto)"},
230
+ "model": {"type": "string", "enum": ["auto", "relight", "background", "world", "video"], "description": "Engine (default auto)"},
231
+ "environments": {"type": "object", "description": "background/world: what to put behind the work cell (default: 3 matched to the rig). One of ids, prompt, or filter/count", "properties": {
232
+ "ids": {"type": "array", "items": {"type": "string"}, "description": "Environment ids (aenv_...) or library ids"},
233
+ "filter": {"type": "object", "properties": {"category": {"type": "string"}, "lighting": {"type": "string", "enum": ["daylight", "artificial"]}}},
234
+ "prompt": {"type": "string", "description": "Make new environments from a description (minutes)"},
235
+ "count": {"type": "integer", "description": "How many to pick or make"}}},
236
+ "must_keep": {"type": "array", "items": {"type": "string"}, "description": "Things that must keep their recorded pixels, besides the robot and what it handles"},
237
+ "variants": {"type": "integer", "description": "background/world: environments per source episode (default 1)"},
238
+ "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)"},
239
+ "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)"},
240
+ "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"},
220
241
  "reference_images": {"type": "array", "maxItems": 6, "description": "Photos of the target look", "items": {
221
242
  "type": "object", "properties": {
222
243
  "camera": {"type": "string", "description": "Camera it shows (e.g. 'top'); omit for all cameras"},
@@ -276,42 +297,42 @@ TOOLS = [
276
297
  "twin_id": {"type": "string", "description": "Twin id (twin_...)"}},
277
298
  "required": ["twin_id"]}, annotations=_WRITE),
278
299
 
279
- # Sim runs (public /v1 API): a Kite SDK spec on Kite's machines -> a scorecard and a verifiable run directory
280
- Tool(name="kite_sim_validate", title="Validate Sim Run",
300
+ # Evaluations (public /v1 API): a Kite SDK spec on Kite's machines -> a scorecard and a verifiable run directory
301
+ Tool(name="kite_eval_validate", title="Validate Evaluation",
281
302
  description="Free check of a Kite SDK spec (kite_spec_version \"2\") before it runs on Kite's machines, in seconds: the spec parses and plans (episodes, each evaluator, the estimate), every file it names is accounted for, and the price (price.reserved tokens at price.tokens_per_hour: an NVIDIA L4 for a cuda policy, a CPU machine otherwise). Creates nothing and charges nothing. Errors keep the SDK's own code, field, fix and line (spec_invalid, files_required listing each missing path, code_not_allowed for Python Kite does not ship).",
282
- inputSchema={"type": "object", "properties": _SIM_SPEC}, annotations=_READ),
283
- Tool(name="kite_sim_run", title="Start Sim Run",
284
- description="Run a Kite SDK spec on Kite's machines: the policy in the spec's world for its episodes, every episode traced and every evaluator scored. Returns the queued run (id srun_...) with a `next` instruction; follow it with kite_sim_status. The run's cap (max_minutes, default 60 on CPU and 120 on an L4) is reserved now at its tier's rate, and the run is charged the minutes its job ran; a failed run costs nothing (insufficient_tokens when the balance is short). A finished run has a scorecard (kite_sim_report), its manifest hash (the run's identity, shared by a local `kite run` of the same spec) and the run directory as one .tar.gz.",
303
+ inputSchema={"type": "object", "properties": _EVAL_SPEC}, annotations=_READ),
304
+ Tool(name="kite_eval_run", title="Start Evaluation",
305
+ description="Run a Kite SDK spec on Kite's machines: the policy in the spec's world for its episodes, every episode traced and every evaluator scored. Returns the queued run (id eval_...) with a `next` instruction; follow it with kite_eval_status. The run's cap (max_minutes, default 60 on CPU and 120 on an L4) is reserved now at its tier's rate, and the run is charged the minutes its job ran; a failed run costs nothing (insufficient_tokens when the balance is short). A finished run has a scorecard (kite_eval_report), its manifest hash (the run's identity, shared by a local `kite run` of the same spec) and the run directory as one .tar.gz.",
285
306
  inputSchema={"type": "object", "properties": {
286
- **_SIM_SPEC,
307
+ **_EVAL_SPEC,
287
308
  "max_minutes": {"type": "integer", "description": "The run's cap, 1-720: what it reserves and the most it can cost. Optional"},
288
309
  "label": {"type": "string", "description": "A short name for your own listing. Optional"},
289
310
  "no_eval": {"type": "boolean", "description": "Run the episodes only: no evaluators, no scorecard. Optional"},
290
311
  "verify_determinism": {"type": "boolean", "description": "Re-run episode 0 and fail the run on a differing hash. Optional"},
291
312
  "idempotency_key": {"type": "string", "description": "Reuse the same key when retrying, so a retry never runs twice. Optional"}}},
292
313
  annotations=_WRITE),
293
- Tool(name="kite_sim_status", title="Get Sim Run Status",
294
- description="Get a sim run's status (queued/processing/canceling/succeeded/failed/canceled), its phase (materializing, planning, fetching, running, verifying, packaging), the episodes done, the tokens reserved and refunded, and a `next` instruction. queued includes waiting for a machine: an L4 can take minutes to find. When succeeded it carries the result (manifest_hash, run_hash, verified, each evaluator's metric with its interval) and `next` is the exact command that fetches and extracts the run directory. A failed run carries the SDK's error (code, param, details.kite_code, details.remediation).",
295
- inputSchema={"type": "object", "properties": _SIM_RUN_ID, "required": ["run_id"]}, annotations=_READ),
296
- Tool(name="kite_sim_list", title="List Sim Runs",
297
- description="List your sim runs, newest first, with their last known status (kite_sim_status has live progress).",
314
+ Tool(name="kite_eval_status", title="Get Evaluation Status",
315
+ description="Get an evaluation's status (queued/processing/canceling/succeeded/failed/canceled), its phase (materializing, planning, fetching, running, verifying, packaging), the episodes done, the tokens reserved and refunded, and a `next` instruction. queued includes waiting for a machine: an L4 can take minutes to find. When succeeded it carries the result (manifest_hash, run_hash, verified, each evaluator's metric with its interval) and `next` is the exact command that fetches and extracts the run directory. A failed run carries the SDK's error (code, param, details.kite_code, details.remediation).",
316
+ inputSchema={"type": "object", "properties": _EVAL_ID, "required": ["run_id"]}, annotations=_READ),
317
+ Tool(name="kite_eval_list", title="List Evaluations",
318
+ description="List your evaluations, newest first, with their last known status (kite_eval_status has live progress).",
298
319
  inputSchema={"type": "object", "properties": {
299
320
  "limit": {"type": "integer", "description": "Max rows (1-100, default 20)"}}}, annotations=_READ),
300
- Tool(name="kite_sim_report", title="Get Sim Run Scorecard",
301
- description="Get a succeeded sim run's scorecard, the SDK's scorecard.json: every evaluator's value, confidence interval and count, the calibration it was scored under, and what was not measured.",
302
- inputSchema={"type": "object", "properties": _SIM_RUN_ID, "required": ["run_id"]}, annotations=_READ),
303
- Tool(name="kite_sim_logs", title="Get Sim Run Logs",
304
- description="Get the last lines the SDK printed for a sim run, oldest first; readable while it runs and after. Use it to see why a run failed.",
321
+ Tool(name="kite_eval_report", title="Get Evaluation Scorecard",
322
+ description="Get a succeeded evaluation's scorecard, the SDK's scorecard.json: every evaluator's value, confidence interval and count, the calibration it was scored under, and what was not measured.",
323
+ inputSchema={"type": "object", "properties": _EVAL_ID, "required": ["run_id"]}, annotations=_READ),
324
+ Tool(name="kite_eval_logs", title="Get Evaluation Logs",
325
+ description="Get the last lines the SDK printed for an evaluation, oldest first; readable while it runs and after. Use it to see why a run failed.",
305
326
  inputSchema={"type": "object", "properties": {
306
- **_SIM_RUN_ID,
327
+ **_EVAL_ID,
307
328
  "tail": {"type": "integer", "description": "How many of the last lines (default 200)"}},
308
329
  "required": ["run_id"]}, annotations=_READ),
309
- Tool(name="kite_sim_cancel", title="Cancel Sim Run",
310
- description="Stop a queued or running sim run. It answers canceling, and reads canceled once its job has stopped (check with kite_sim_status). It is charged the minutes it ran.",
311
- inputSchema={"type": "object", "properties": _SIM_RUN_ID, "required": ["run_id"]}, annotations=_DESTRUCTIVE),
312
- Tool(name="kite_sim_resume", title="Resume Sim Run",
313
- description="Run a failed or canceled sim run again from its request, as a new attempt: it reserves its cap again and is queued.",
314
- inputSchema={"type": "object", "properties": _SIM_RUN_ID, "required": ["run_id"]}, annotations=_WRITE),
330
+ Tool(name="kite_eval_cancel", title="Cancel Evaluation",
331
+ description="Stop a queued or running evaluation. It answers canceling, and reads canceled once its job has stopped (check with kite_eval_status). It is charged the minutes it ran.",
332
+ inputSchema={"type": "object", "properties": _EVAL_ID, "required": ["run_id"]}, annotations=_DESTRUCTIVE),
333
+ Tool(name="kite_eval_resume", title="Resume Evaluation",
334
+ description="Run a failed or canceled evaluation again from its request, as a new attempt: it reserves its cap again and is queued.",
335
+ inputSchema={"type": "object", "properties": _EVAL_ID, "required": ["run_id"]}, annotations=_WRITE),
315
336
 
316
337
  # RL runs (public /v1 API): train a robot policy in simulation from a spec, one run per seed
317
338
  Tool(name="kite_rl_catalog", title="RL Training Catalog",
@@ -402,11 +423,16 @@ async def _dispatch(name: str, args: dict, client: KiteClient) -> list[TextConte
402
423
 
403
424
  # --- Augmentations (public /v1 API) ---
404
425
  if name == "kite_augment_create":
426
+ from kite_cli.cli.commands.augment import scene_config
427
+
405
428
  body = {
406
429
  "source": {"type": "huggingface", "repo_id": args["repo_id"]},
407
430
  "instructions": args["instructions"],
408
- "config": {"episode_count": args.get("episode_count", 3)},
431
+ "config": scene_config(args.get("episodes", args.get("episode_count")), args.get("model"),
432
+ args.get("variants"), args.get("preview_first"), args.get("allow_video_fallback"),
433
+ args.get("video_check")),
409
434
  "output": {"type": args.get("output_type", "download")},
435
+ **{k: args[k] for k in ("environments", "must_keep") if args.get(k)},
410
436
  }
411
437
  if args.get("hf_repo"):
412
438
  body["output"]["repo_id"] = args["hf_repo"]
@@ -453,32 +479,32 @@ async def _dispatch(name: str, args: dict, client: KiteClient) -> list[TextConte
453
479
  if name == "kite_twin_resume":
454
480
  return _ok(_twin_view(client.post(f"/v1/twins/{args['twin_id']}/resume"), client))
455
481
 
456
- # --- Sim runs (public /v1 API) ---
457
- if name in ("kite_sim_validate", "kite_sim_run"):
482
+ # --- Evaluations (public /v1 API) ---
483
+ if name in ("kite_eval_validate", "kite_eval_run"):
458
484
  try:
459
- body, validation = _sim_request(args, client)
485
+ body, validation = _eval_request(args, client)
460
486
  except ValueError as e:
461
487
  return _err(str(e))
462
- if name == "kite_sim_validate":
463
- return _ok(validation or client.post("/v1/sim_runs/validate", json_data=body))
464
- if name == "kite_sim_run":
488
+ if name == "kite_eval_validate":
489
+ return _ok(validation or client.post("/v1/evaluations/validate", json_data=body))
490
+ if name == "kite_eval_run":
465
491
  options = {k: args[k] for k in ("max_minutes", "no_eval", "verify_determinism") if args.get(k)}
466
492
  body = {**body, **({"options": options} if options else {}),
467
493
  **({"label": args["label"]} if args.get("label") else {})}
468
494
  headers = {"Idempotency-Key": args["idempotency_key"]} if args.get("idempotency_key") else None
469
- return _ok(_sim_view(client.post("/v1/sim_runs", json_data=body, extra_headers=headers), client))
470
- if name == "kite_sim_status":
471
- return _ok(_sim_view(client.get(f"/v1/sim_runs/{args['run_id']}"), client))
472
- if name == "kite_sim_list":
473
- return _ok(client.get("/v1/sim_runs", params={"limit": args.get("limit", 20)}))
474
- if name == "kite_sim_report":
475
- return _ok(client.get(f"/v1/sim_runs/{args['run_id']}/report"))
476
- if name == "kite_sim_logs":
477
- return _ok(client.get(f"/v1/sim_runs/{args['run_id']}/logs", params={"tail": args.get("tail", 200)}))
478
- if name == "kite_sim_cancel":
479
- return _ok(_sim_view(client.post(f"/v1/sim_runs/{args['run_id']}/cancel"), client))
480
- if name == "kite_sim_resume":
481
- return _ok(_sim_view(client.post(f"/v1/sim_runs/{args['run_id']}/resume"), client))
495
+ return _ok(_eval_view(client.post("/v1/evaluations", json_data=body, extra_headers=headers), client))
496
+ if name == "kite_eval_status":
497
+ return _ok(_eval_view(client.get(f"/v1/evaluations/{args['run_id']}"), client))
498
+ if name == "kite_eval_list":
499
+ return _ok(client.get("/v1/evaluations", params={"limit": args.get("limit", 20)}))
500
+ if name == "kite_eval_report":
501
+ return _ok(client.get(f"/v1/evaluations/{args['run_id']}/report"))
502
+ if name == "kite_eval_logs":
503
+ return _ok(client.get(f"/v1/evaluations/{args['run_id']}/logs", params={"tail": args.get("tail", 200)}))
504
+ if name == "kite_eval_cancel":
505
+ return _ok(_eval_view(client.post(f"/v1/evaluations/{args['run_id']}/cancel"), client))
506
+ if name == "kite_eval_resume":
507
+ return _ok(_eval_view(client.post(f"/v1/evaluations/{args['run_id']}/resume"), client))
482
508
 
483
509
  # --- RL runs (public /v1 API) ---
484
510
  if name == "kite_rl_catalog":
File without changes
File without changes