kiteml-cli 0.4.0__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.4.0 → kiteml_cli-0.4.1}/PKG-INFO +1 -1
  2. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/__init__.py +1 -1
  3. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/augment.py +12 -5
  4. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/client.py +11 -0
  5. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/mcp/server.py +22 -11
  6. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/.gitignore +0 -0
  7. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/README.md +0 -0
  8. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/pyproject.toml +0 -0
  9. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/archive.py +0 -0
  10. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/auth.py +0 -0
  11. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/__init__.py +0 -0
  12. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/__init__.py +0 -0
  13. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/auth_cmd.py +0 -0
  14. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/config_cmd.py +0 -0
  15. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/evaluations.py +0 -0
  16. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/rl.py +0 -0
  17. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/commands/twin.py +0 -0
  18. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/cli/main.py +0 -0
  19. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/config.py +0 -0
  20. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/mcp/__init__.py +0 -0
  21. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/mcp/common.py +0 -0
  22. {kiteml_cli-0.4.0 → kiteml_cli-0.4.1}/src/kite_cli/output.py +0 -0
  23. {kiteml_cli-0.4.0 → 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.4.0
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
@@ -1,3 +1,3 @@
1
1
  """Kite CLI and MCP server for the Kite robotics training platform."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.1"
@@ -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) -> dict:
42
- """The /v1 ``config`` for an episode selection ("all" or a count) and the scene engines' options: background /
43
- world augment whole datasets and take ``episodes``; every other model takes a count as ``episode_count`` ("all"
44
- sends none: relight covers every episode, video and auto keep their default)."""
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,6 +57,8 @@ 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
 
@@ -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:
@@ -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 ""))
@@ -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
- 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
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")
@@ -228,6 +237,7 @@ TOOLS = [
228
237
  "variants": {"type": "integer", "description": "background/world: environments per source episode (default 1)"},
229
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)"},
230
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"},
231
241
  "reference_images": {"type": "array", "maxItems": 6, "description": "Photos of the target look", "items": {
232
242
  "type": "object", "properties": {
233
243
  "camera": {"type": "string", "description": "Camera it shows (e.g. 'top'); omit for all cameras"},
@@ -419,7 +429,8 @@ async def _dispatch(name: str, args: dict, client: KiteClient) -> list[TextConte
419
429
  "source": {"type": "huggingface", "repo_id": args["repo_id"]},
420
430
  "instructions": args["instructions"],
421
431
  "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")),
432
+ args.get("variants"), args.get("preview_first"), args.get("allow_video_fallback"),
433
+ args.get("video_check")),
423
434
  "output": {"type": args.get("output_type", "download")},
424
435
  **{k: args[k] for k in ("environments", "must_keep") if args.get(k)},
425
436
  }
File without changes
File without changes
File without changes