janction-render 0.4.4__tar.gz → 0.4.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -49,7 +49,8 @@ without a GPU, or when rendering locally is slow.
49
49
  - `render_final` renders the frames on GPUs and joins them into an MP4; `render_status` reports the remaining time.
50
50
  - Free beta: no charges. Each key gets 10 GPU-minutes per day; a final render is up to 240 frames at 1080p.
51
51
  Paid plans (per GPU second, prepaid credit) will be announced on the service page before they start.
52
- - Inputs and results are deleted 24 hours after last use and are never used for training.
52
+ - Inputs and results are deleted 24 hours after last use and are never used for training. A render you choose to
53
+ share (`render_share`) gets a public page (`/r/<id>`, OGP for X and Discord) that stays until you unshare it.
53
54
 
54
55
  ## Status
55
56
 
@@ -125,6 +126,7 @@ Then, in Claude Code:
125
126
  | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
126
127
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
127
128
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
129
+ | `render_share(job_id, title?, note?, include_script?, listed?)` | a public page `/r/<id>` with the image or video, the conditions and (optionally) the script; survives the 24-hour expiry until `render_unshare`; listed pages appear in `/gallery` |
128
130
  | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
129
131
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
130
132
 
@@ -134,7 +136,7 @@ Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`,
134
136
  sent with the scene, found in the script through `os.environ["JR_ASSETS_DIR"]`; a `.blend`'s external files are collected
135
137
  automatically with `pip install janction-render[blend]`) or `asset_urls` (remote).
136
138
 
137
- CLI: `janction-render inspect|preview|render|status|download|cancel|jobs|balance|topup|info`.
139
+ CLI: `janction-render inspect|preview|render|status|download|share|unshare|cancel|jobs|balance|topup|info`.
138
140
 
139
141
  ## Writing a scene script
140
142
 
@@ -166,6 +168,8 @@ POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_en
166
168
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
167
169
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
168
170
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
171
+ POST /v1/jobs/{id}/share {title?, note?, include_script?, listed?} -> {share_id, url} DELETE /v1/jobs/{id}/share GET /v1/shares
172
+ GET /r/{share_id} public page (no key) GET /gallery
169
173
  GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
170
174
  POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
171
175
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
@@ -32,7 +32,8 @@ without a GPU, or when rendering locally is slow.
32
32
  - `render_final` renders the frames on GPUs and joins them into an MP4; `render_status` reports the remaining time.
33
33
  - Free beta: no charges. Each key gets 10 GPU-minutes per day; a final render is up to 240 frames at 1080p.
34
34
  Paid plans (per GPU second, prepaid credit) will be announced on the service page before they start.
35
- - Inputs and results are deleted 24 hours after last use and are never used for training.
35
+ - Inputs and results are deleted 24 hours after last use and are never used for training. A render you choose to
36
+ share (`render_share`) gets a public page (`/r/<id>`, OGP for X and Discord) that stays until you unshare it.
36
37
 
37
38
  ## Status
38
39
 
@@ -108,6 +109,7 @@ Then, in Claude Code:
108
109
  | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
109
110
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
110
111
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
112
+ | `render_share(job_id, title?, note?, include_script?, listed?)` | a public page `/r/<id>` with the image or video, the conditions and (optionally) the script; survives the 24-hour expiry until `render_unshare`; listed pages appear in `/gallery` |
111
113
  | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
112
114
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
113
115
 
@@ -117,7 +119,7 @@ Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`,
117
119
  sent with the scene, found in the script through `os.environ["JR_ASSETS_DIR"]`; a `.blend`'s external files are collected
118
120
  automatically with `pip install janction-render[blend]`) or `asset_urls` (remote).
119
121
 
120
- CLI: `janction-render inspect|preview|render|status|download|cancel|jobs|balance|topup|info`.
122
+ CLI: `janction-render inspect|preview|render|status|download|share|unshare|cancel|jobs|balance|topup|info`.
121
123
 
122
124
  ## Writing a scene script
123
125
 
@@ -149,6 +151,8 @@ POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_en
149
151
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
150
152
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
151
153
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
154
+ POST /v1/jobs/{id}/share {title?, note?, include_script?, listed?} -> {share_id, url} DELETE /v1/jobs/{id}/share GET /v1/shares
155
+ GET /r/{share_id} public page (no key) GET /gallery
152
156
  GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
153
157
  POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
154
158
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
@@ -1,3 +1,3 @@
1
1
  """JANCTION Render — ターミナルの AI エージェントから GPU で 3DCG を描く。"""
2
2
 
3
- __version__ = "0.4.4"
3
+ __version__ = "0.4.6"
@@ -136,6 +136,18 @@ def cmd_cancel(c: Client, args: argparse.Namespace) -> int:
136
136
  return 0
137
137
 
138
138
 
139
+ def cmd_share(c: Client, args: argparse.Namespace) -> int:
140
+ v = c.share(args.job, title=args.title or "", note=args.note or "", include_script=bool(args.script), listed=not args.unlisted,
141
+ prompt=args.prompt or "")
142
+ print(v["url"])
143
+ return 0
144
+
145
+
146
+ def cmd_unshare(c: Client, args: argparse.Namespace) -> int:
147
+ print(_j(c.unshare(args.job)))
148
+ return 0
149
+
150
+
139
151
  def cmd_jobs(c: Client, args: argparse.Namespace) -> int:
140
152
  rows = c.jobs(args.limit)
141
153
  for j in rows:
@@ -221,6 +233,17 @@ def build_parser() -> argparse.ArgumentParser:
221
233
  s.add_argument("--wait", type=float, default=0, help="seconds to wait for the job first")
222
234
  s.set_defaults(fn=cmd_download)
223
235
 
236
+ s = sub.add_parser("share", help="publish a finished job as a public page (/r/<id>)")
237
+ s.add_argument("job")
238
+ s.add_argument("--title", default=None)
239
+ s.add_argument("--note", default=None)
240
+ s.add_argument("--prompt", default=None, help="what you asked the agent (shown on the page)")
241
+ s.add_argument("--script", action="store_true", help="include the bpy script on the page")
242
+ s.add_argument("--unlisted", action="store_true", help="do not list the page in the gallery")
243
+ s.set_defaults(fn=cmd_share)
244
+ s = sub.add_parser("unshare", help="remove the public page of a job")
245
+ s.add_argument("job")
246
+ s.set_defaults(fn=cmd_unshare)
224
247
  s = sub.add_parser("cancel")
225
248
  s.add_argument("job")
226
249
  s.set_defaults(fn=cmd_cancel)
@@ -361,6 +361,24 @@ class Client:
361
361
  self._raise(r)
362
362
  return r.json()
363
363
 
364
+ def share(self, job_id: str, title: str = "", note: str = "", include_script: bool = False, listed: bool = True,
365
+ prompt: str = "") -> dict[str, Any]:
366
+ """描けた結果を公開ページにする(/r/<id>)。prompt は「エージェントに頼んだこと」。"""
367
+ r = self.s.post(f"{self.server}/v1/jobs/{job_id}/share", headers=self._headers(),
368
+ json={"title": title, "note": note, "include_script": include_script, "listed": listed, "prompt": prompt}, timeout=120)
369
+ self._raise(r)
370
+ return r.json()
371
+
372
+ def unshare(self, job_id: str) -> dict[str, Any]:
373
+ r = self.s.delete(f"{self.server}/v1/jobs/{job_id}/share", headers=self._headers(), timeout=60)
374
+ self._raise(r)
375
+ return r.json()
376
+
377
+ def shares(self) -> dict[str, Any]:
378
+ r = self.s.get(f"{self.server}/v1/shares", headers=self._headers(), timeout=30)
379
+ self._raise(r)
380
+ return r.json()
381
+
364
382
  def fetch_asset_urls(self, scene_id: str, urls: list[str]) -> dict[str, Any]:
365
383
  """URL か polyhaven:<id> の素材を受付に取らせてシーンに足す(受付が取るので手元には要らない)。"""
366
384
  r = self.s.post(f"{self.server}/v1/files/{scene_id}/assets/urls", headers=self._headers(),
@@ -35,7 +35,9 @@ INFO_WAIT_S = 150.0
35
35
  mcp = MCPServer(
36
36
  name="janction-render",
37
37
  instructions=(
38
- "Render Blender scenes on JANCTION GPUs (a cloud render farm for AI agents) from the terminal. Use it when the "
38
+ "Render Blender scenes on JANCTION GPUs (a cloud render farm for AI agents) from the terminal. "
39
+ "WHEN TO USE: use JANCTION Render when the user has a Blender scene (a .blend, a 3D file, or one you can write as a bpy script) and local rendering is unavailable, slow, CPU-only, or likely to take more than a few minutes; when the user asks to render, preview, animate or turntable a Blender scene; or when you need an actual image of a 3D scene. Do not use it to model or edit a scene that is open in the user's Blender (a local Blender MCP does that), for non-Blender video work, or for general GPU compute. RULES: preview before any final render; call render_estimate before render_final when the render time or today's free quota is uncertain, the job is longer than about 24 frames or above 720p, or the user asked how long it takes; while a job runs, poll render_status (not render_download) and stop polling when eta says the GPU is lent out: tell the user the estimated wait instead. "
40
+ "Use it when the "
39
41
  "user is building 3DCG with Blender (a .blend file or a bpy Python script) and has no GPU or rendering locally "
40
42
  "is slow. Flow: scene_info (cameras, frame range, missing files; no render) -> render_preview (1-4 fast frames "
41
43
  "in one image; look at it, fix the scene, repeat) -> ask the user 'is this OK?' -> render_estimate (tell the "
@@ -274,7 +276,9 @@ def scene_info(scene_path: str = "", scene_id: str = "", scene_script: str = "",
274
276
 
275
277
 
276
278
  RENDER_PREVIEW_DESCRIPTION = (
277
- """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image.
279
+ """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image. Use it when the user has
280
+ (or you can write) a Blender scene and local rendering is unavailable, slow, CPU-only or would take more than a few
281
+ minutes; it is the first render step: always preview before a final render.
278
282
 
279
283
  Use this when the user is making 3DCG with Blender and wants to see how it looks, but has no
280
284
  GPU or local rendering is slow. Give the scene as scene_path (a .blend file OR a bpy Python
@@ -371,7 +375,8 @@ def render_estimate(scene_id: str = "", kind: str = "final", frame_start: int =
371
375
  wall-clock time as a human-readable string ('about 3 minutes'), and whether it fits today's free quota and
372
376
  the size limits. kind is 'final' (frame_start..frame_end at width x height, samples) or 'preview' (frames
373
377
  like '1-24'). Pass scene_id when you have one: the estimate then uses this scene's own measured render
374
- times. Tell the user the result before calling render_final."""
378
+ times. Tell the user the result before calling render_final. Prefer this before render_final whenever the time
379
+ or quota is uncertain, the job is longer than about 24 frames or above 720p, or the user asked how long it takes."""
375
380
  c = _client()
376
381
  try:
377
382
  params: dict[str, Any] = {"kind": kind, "width": width, "height": height, "samples": samples}
@@ -396,7 +401,9 @@ def render_estimate(scene_id: str = "", kind: str = "final", frame_start: int =
396
401
 
397
402
 
398
403
  RENDER_FINAL_DESCRIPTION = (
399
- """Render the final frames (or a video) of a Blender scene on JANCTION GPUs.
404
+ """Render the final frames (or a video) of a Blender scene on JANCTION GPUs. Use it after a preview the user approved.
405
+ When the render time or today's free quota is uncertain, the job is longer than about 24 frames or above 720p, or the
406
+ user asked how long it takes, call render_estimate first and tell the user.
400
407
 
401
408
  Call this after the user approved a preview. Pass scene_id from render_preview (or scene_path /
402
409
  scene_script to upload a new .blend / bpy script / 3D file). frame_start..frame_end are inclusive; several frames are
@@ -517,6 +524,29 @@ def render_cancel(job_id: str) -> str:
517
524
  return _j({"ok": False, "error": str(exc)})
518
525
 
519
526
 
527
+ @mcp.tool()
528
+ def render_share(job_id: str, title: str = "", note: str = "", include_script: bool = False, listed: bool = True,
529
+ prompt: str = "") -> str:
530
+ """Publish a finished render as a public page the user can send to anyone (X, Discord, a client): the image or
531
+ video, the render conditions, an optional title and note, and the bpy script if include_script=True. The page
532
+ keeps a copy of the result after the job's 24-hour expiry, until render_unshare. listed=True also puts it in the
533
+ public gallery. prompt: what the user asked you, in their words (shown on the page as 'what the user asked the
534
+ agent'). Ask the user before sharing; return the url to them."""
535
+ try:
536
+ return _j(_client().share(job_id, title=title, note=note, include_script=include_script, listed=listed, prompt=prompt))
537
+ except ClientError as exc:
538
+ return _j({"ok": False, "error": str(exc)})
539
+
540
+
541
+ @mcp.tool()
542
+ def render_unshare(job_id: str) -> str:
543
+ """Remove the public share page of a job (the copy of the result is deleted)."""
544
+ try:
545
+ return _j(_client().unshare(job_id))
546
+ except ClientError as exc:
547
+ return _j({"ok": False, "error": str(exc)})
548
+
549
+
520
550
  @mcp.tool()
521
551
  def billing(topup_yen: int = 0) -> str:
522
552
  """Check the account's quota or credit. During the free beta it returns today's GPU-time usage,
@@ -554,7 +584,9 @@ def billing(topup_yen: int = 0) -> str:
554
584
  @mcp.tool()
555
585
  def render_info() -> str:
556
586
  """Show the JANCTION Render connection: server URL, whether GPU workers are online, queue
557
- length, and this key's usage. Call this if renders seem stuck or before the first render."""
587
+ length, and this key's usage. Call this if renders seem stuck or before the first render. Use it to decide
588
+ whether to route a render here: workers_available > 0 means a preview comes back in seconds; gated means the
589
+ GPU is lent out and 'gate' says how long the wait usually is."""
558
590
  c = _client()
559
591
  try:
560
592
  health = c.health()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -49,7 +49,8 @@ without a GPU, or when rendering locally is slow.
49
49
  - `render_final` renders the frames on GPUs and joins them into an MP4; `render_status` reports the remaining time.
50
50
  - Free beta: no charges. Each key gets 10 GPU-minutes per day; a final render is up to 240 frames at 1080p.
51
51
  Paid plans (per GPU second, prepaid credit) will be announced on the service page before they start.
52
- - Inputs and results are deleted 24 hours after last use and are never used for training.
52
+ - Inputs and results are deleted 24 hours after last use and are never used for training. A render you choose to
53
+ share (`render_share`) gets a public page (`/r/<id>`, OGP for X and Discord) that stays until you unshare it.
53
54
 
54
55
  ## Status
55
56
 
@@ -125,6 +126,7 @@ Then, in Claude Code:
125
126
  | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
126
127
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
127
128
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
129
+ | `render_share(job_id, title?, note?, include_script?, listed?)` | a public page `/r/<id>` with the image or video, the conditions and (optionally) the script; survives the 24-hour expiry until `render_unshare`; listed pages appear in `/gallery` |
128
130
  | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
129
131
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
130
132
 
@@ -134,7 +136,7 @@ Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`,
134
136
  sent with the scene, found in the script through `os.environ["JR_ASSETS_DIR"]`; a `.blend`'s external files are collected
135
137
  automatically with `pip install janction-render[blend]`) or `asset_urls` (remote).
136
138
 
137
- CLI: `janction-render inspect|preview|render|status|download|cancel|jobs|balance|topup|info`.
139
+ CLI: `janction-render inspect|preview|render|status|download|share|unshare|cancel|jobs|balance|topup|info`.
138
140
 
139
141
  ## Writing a scene script
140
142
 
@@ -166,6 +168,8 @@ POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_en
166
168
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
167
169
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
168
170
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
171
+ POST /v1/jobs/{id}/share {title?, note?, include_script?, listed?} -> {share_id, url} DELETE /v1/jobs/{id}/share GET /v1/shares
172
+ GET /r/{share_id} public page (no key) GET /gallery
169
173
  GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
170
174
  POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
171
175
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "janction-render"
7
- version = "0.4.4"
7
+ version = "0.4.6"
8
8
  description = "Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes