@genex-ai/cli-demo 1.30.0-dev.644 → 1.31.0

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 (47) hide show
  1. package/README.md +0 -18
  2. package/dist/index.js +2839 -4768
  3. package/package.json +3 -3
  4. package/templates/controllers/character/follow-camera.ts +1 -16
  5. package/templates/controllers/character/meshy/meshy-loader.ts +2 -3
  6. package/templates/controllers/quality/pick-asset.ts +16 -49
  7. package/templates/controllers/shared/physics-world.ts +4 -6
  8. package/templates/skills/genex-ai-character/SKILL.md +15 -77
  9. package/templates/skills/genex-ai-menu/SKILL.md +11 -15
  10. package/templates/skills/genex-ai-model/SKILL.md +7 -45
  11. package/templates/skills/genex-ai-texture/SKILL.md +1 -1
  12. package/templates/skills/genex-ai-video/SKILL.md +17 -75
  13. package/templates/skills/genex-game-director/SKILL.md +46 -112
  14. package/templates/skills/genex-game-director/references/design-contract.md +5 -13
  15. package/templates/skills/genex-game-director/references/routing-map.md +45 -30
  16. package/templates/skills/genex-getting-started/SKILL.md +2 -2
  17. package/templates/skills/genex-monetization/SKILL.md +171 -0
  18. package/templates/skills/genex-threejs-adaptive-quality/SKILL.md +0 -21
  19. package/templates/skills/genex-threejs-character-controller/SKILL.md +5 -17
  20. package/templates/skills/genex-threejs-creatures/SKILL.md +1 -8
  21. package/templates/skills/genex-threejs-embed-auth/SKILL.md +52 -8
  22. package/templates/skills/genex-threejs-game-ui/SKILL.md +6 -55
  23. package/templates/skills/genex-threejs-procedural-assets/SKILL.md +10 -17
  24. package/templates/skills/genex-threejs-visual-validation/SKILL.md +2 -12
  25. package/templates/skills/genex-tool-audio/SKILL.md +2 -3
  26. package/templates/skills/genex-tool-character/SKILL.md +5 -36
  27. package/templates/skills/genex-tool-image/SKILL.md +2 -4
  28. package/templates/skills/genex-tool-model/SKILL.md +6 -32
  29. package/templates/skills/genex-tool-texture/SKILL.md +1 -1
  30. package/templates/skills/genex-tool-video/SKILL.md +5 -28
  31. package/templates/skills/genex-tool-workflow/SKILL.md +1 -4
  32. package/templates/skills/genex-updates/SKILL.md +1 -1
  33. package/dist/blender-mcp-Q6PSFYSE.js +0 -241
  34. package/dist/blender-serve-BF4FZ55Z.js +0 -244
  35. package/dist/chunk-2COG4P3T.js +0 -968
  36. package/dist/chunk-HYCSNWYX.js +0 -126
  37. package/templates/blender-service/demo/castle.py +0 -117
  38. package/templates/blender-service/gpu_witness.py +0 -245
  39. package/templates/blender-service/ops.py +0 -225
  40. package/templates/blender-service/pool.py +0 -910
  41. package/templates/blender-service/server.py +0 -611
  42. package/templates/blender-service/supervisor.py +0 -221
  43. package/templates/blender-service/views.py +0 -281
  44. package/templates/controllers/quality/deadline.ts +0 -117
  45. package/templates/skills/genex-blender-scene/SKILL.md +0 -243
  46. package/templates/skills/genex-lane-card/SKILL.md +0 -78
  47. package/templates/skills/genex-tool-publish/SKILL.md +0 -100
@@ -1,225 +0,0 @@
1
- """Scene operations. Every mutating call runs on the HTTP server's own thread,
2
- which is Blender's main thread -- bpy is not thread-safe and the server is
3
- deliberately non-threading so this holds without a queue."""
4
-
5
- import contextlib
6
- import io
7
- import os
8
- import tempfile
9
- import traceback
10
- import urllib.parse
11
- import urllib.request
12
-
13
- import bpy
14
-
15
- from views import scene_bounds
16
-
17
- # The exec namespace PERSISTS across calls, so an agent can define a helper in
18
- # one step and use it in the next. That is the difference between a REPL and a
19
- # series of unrelated scripts.
20
- NAMESPACE = {}
21
-
22
-
23
- def reset_namespace():
24
- import math
25
- import random
26
-
27
- import bmesh
28
- import mathutils
29
-
30
- NAMESPACE.clear()
31
- NAMESPACE.update(
32
- {"bpy": bpy, "bmesh": bmesh, "mathutils": mathutils, "math": math, "random": random}
33
- )
34
-
35
-
36
- def run_script(source):
37
- """Execute arbitrary Python against the live scene.
38
-
39
- Arbitrary by design: a fixed tool vocabulary cannot express 'build a castle'.
40
- The containment story is the container -- disposable, egress-locked in the
41
- hosted path -- never a sanitizer on this string, which does not work.
42
- """
43
- out, err = io.StringIO(), io.StringIO()
44
- error = None
45
- with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
46
- try:
47
- exec(compile(source, "<genex-exec>", "exec"), NAMESPACE)
48
- except Exception:
49
- error = traceback.format_exc(limit=8)
50
- return {"stdout": out.getvalue(), "stderr": err.getvalue(), "error": error}
51
-
52
-
53
- def _tri_count(obj):
54
- if obj.type != "MESH" or obj.data is None:
55
- return 0
56
- return sum(max(len(p.vertices) - 2, 0) for p in obj.data.polygons)
57
-
58
-
59
- def scene_info():
60
- """The numeric truths. These are the GATE -- the picture is for steering.
61
-
62
- A screenshot judge is measurably bad at pass/fail (WorldCoder-Bench: a
63
- screenshot+VLM gate passes 2.7% of outputs), so an agent should assert on
64
- these counts and use the render to decide what to change.
65
- """
66
- objects, total_tris = [], 0
67
- for obj in bpy.context.scene.objects:
68
- tris = _tri_count(obj)
69
- total_tris += tris
70
- objects.append(
71
- {
72
- "name": obj.name,
73
- "type": obj.type,
74
- "tris": tris,
75
- "location": [round(v, 4) for v in obj.location],
76
- "dimensions": [round(v, 4) for v in obj.dimensions],
77
- "materials": [m.name for m in obj.data.materials if m]
78
- if getattr(obj.data, "materials", None)
79
- else [],
80
- "visible": obj.visible_get(),
81
- }
82
- )
83
- center, radius = scene_bounds()
84
- return {
85
- "objectCount": len(objects),
86
- "meshCount": sum(1 for o in objects if o["type"] == "MESH"),
87
- "totalTris": total_tris,
88
- "materialCount": len(bpy.data.materials),
89
- "bounds": {
90
- "center": [round(v, 4) for v in center],
91
- "radius": round(radius, 4),
92
- },
93
- "objects": objects,
94
- }
95
-
96
-
97
- def reset_scene():
98
- bpy.ops.wm.read_factory_settings(use_empty=True)
99
- reset_namespace()
100
- return scene_info()
101
-
102
-
103
- # A picture the agent LOOKS AT goes inline; an artifact the GAME SHIPS goes to
104
- # object storage. Base64 inflates 4/3 plus JSON escaping, so ~7 MB of raw GLB
105
- # already blows RunPod's 10 MB /run ceiling -- and it fails only on long,
106
- # successful sessions that generated a lot of texture, which is the worst time.
107
- INLINE_GLB_MAX_BYTES = 6 * 1024 * 1024
108
-
109
-
110
- def export_glb(path):
111
- os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
112
- bpy.ops.export_scene.gltf(filepath=path, export_format="GLB", use_selection=False)
113
- return {"path": path, "bytes": os.path.getsize(path)}
114
-
115
-
116
- def upload_file(path, url, headers=None):
117
- """PUT the bytes to a presigned URL. The service never holds an account key
118
- -- the caller mints a single-object, short-TTL URL, so code running inside
119
- /exec has nothing durable to steal."""
120
- import urllib.request
121
-
122
- with open(path, "rb") as f:
123
- body = f.read()
124
- req = urllib.request.Request(url, data=body, method="PUT")
125
- for k, v in (headers or {}).items():
126
- req.add_header(k, v)
127
- with urllib.request.urlopen(req, timeout=300) as r: # noqa: S310 - caller-minted
128
- return {"status": r.status, "bytes": len(body)}
129
-
130
-
131
- def save_checkpoint(path):
132
- """Whole-scene snapshot. Measured elsewhere at tens to hundreds of ms, and
133
- it hides inside the agent's think time. What makes a pod disposable."""
134
- os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
135
- bpy.ops.wm.save_as_mainfile(filepath=path, copy=True, compress=True)
136
- return os.path.getsize(path)
137
-
138
-
139
- def load_checkpoint(path):
140
- """Restore a snapshot. NOTE: this invalidates every live bpy reference, so
141
- the exec namespace is reset alongside it -- a held Python handle raises
142
- `StructRNA has been removed` long after the restore, somewhere unrelated."""
143
- bpy.ops.wm.open_mainfile(filepath=path)
144
- reset_namespace()
145
-
146
-
147
- def _host_allowed(url):
148
- """Empty allowlist = permissive, which is correct for the admin-only spike
149
- and wrong for the hosted lane. GENEX_BLENDER_IMPORT_HOSTS narrows it without
150
- a code change when this reaches a session."""
151
- allow = [h.strip() for h in os.environ.get("GENEX_BLENDER_IMPORT_HOSTS", "").split(",") if h.strip()]
152
- if not allow:
153
- return True
154
- host = (urllib.parse.urlparse(url).hostname or "").lower()
155
- return any(host == a.lower() or host.endswith("." + a.lower().lstrip(".")) for a in allow)
156
-
157
-
158
- def import_glb(url):
159
- if not _host_allowed(url):
160
- raise ValueError(f"import host not allowed: {urllib.parse.urlparse(url).hostname}")
161
- before = {o.name for o in bpy.context.scene.objects}
162
- fd, tmp = tempfile.mkstemp(suffix=".glb", prefix="genex-import-")
163
- os.close(fd)
164
- try:
165
- with urllib.request.urlopen(url, timeout=60) as r: # noqa: S310 - allowlisted above
166
- with open(tmp, "wb") as f:
167
- f.write(r.read())
168
- bpy.ops.import_scene.gltf(filepath=tmp)
169
- finally:
170
- os.unlink(tmp)
171
- added = [o.name for o in bpy.context.scene.objects if o.name not in before]
172
- return {"imported": added, "count": len(added)}
173
-
174
-
175
- def scene_digest():
176
- """A fingerprint of everything a render depends on, for the sheet-repeat guard.
177
-
178
- Built from the data blocks directly, not from scene_info(): that summary
179
- carries location, dimensions and material NAMES, and nothing else — so a
180
- rotation, a scale, a modifier, a moved vertex, a dimmed light or a recoloured
181
- material all hashed identically and the model was told the picture was
182
- unchanged (review finding). Vertex coordinates go through numpy, so a 600k
183
- triangle mesh costs one foreach_get and one hash rather than a Python loop.
184
- """
185
- import hashlib
186
- h = hashlib.sha256()
187
- scene = bpy.context.scene
188
- for obj in scene.objects:
189
- # The RNA transform, NOT matrix_world: a script that sets rotation_euler
190
- # leaves matrix_world stale until the depsgraph evaluates, which in
191
- # background mode is not before this runs. Measured live: a rotation
192
- # hashed identical through matrix_world and the guard said "unchanged".
193
- parent = obj.parent.name if obj.parent else ""
194
- h.update(("%s|%s|%s|%s|%s|%s|%d|%s\n" % (
195
- obj.name, obj.type, parent, tuple(obj.location),
196
- tuple(obj.rotation_euler), tuple(obj.scale),
197
- obj.hide_render, [m.type for m in getattr(obj, "modifiers", [])],
198
- )).encode("utf-8"))
199
- data = obj.data
200
- if obj.type == "MESH" and data is not None:
201
- n = len(data.vertices)
202
- if n:
203
- try:
204
- import numpy as np
205
- buf = np.empty(n * 3, dtype=np.float32)
206
- data.vertices.foreach_get("co", buf)
207
- h.update(buf.tobytes())
208
- except Exception:
209
- h.update(str(n).encode())
210
- for m in data.materials:
211
- if m is None:
212
- continue
213
- h.update(("mat:%s:%s\n" % (m.name, tuple(m.diffuse_color))).encode())
214
- if m.use_nodes and m.node_tree:
215
- for node in m.node_tree.nodes:
216
- if node.type == "BSDF_PRINCIPLED":
217
- h.update(("bsdf:%s\n" % (tuple(node.inputs["Base Color"].default_value),)).encode())
218
- elif obj.type == "LIGHT" and data is not None:
219
- h.update(("light:%s:%s:%s\n" % (data.type, data.energy, tuple(data.color))).encode())
220
- elif obj.type == "CAMERA" and data is not None:
221
- h.update(("cam:%s:%s\n" % (data.lens, data.type)).encode())
222
- w = scene.world
223
- if w is not None:
224
- h.update(("world:%s\n" % (tuple(getattr(w, "color", (0, 0, 0))),)).encode())
225
- return h.hexdigest()