creative-pipeline-mcp 0.2.14-alpha.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 (196) hide show
  1. package/LICENSE +18 -0
  2. package/NOTICE +9 -0
  3. package/README.md +205 -0
  4. package/SECURITY.md +19 -0
  5. package/docs/API_TOOLS.md +74 -0
  6. package/docs/ARCHITECTURE.md +28 -0
  7. package/docs/ARTIFACT_SCHEMA.md +43 -0
  8. package/docs/BLENDER_BRIDGE_STATUS_SCHEMA.md +42 -0
  9. package/docs/CEP_STATUS_SCHEMA.md +35 -0
  10. package/docs/COMPATIBILITY_MATRIX.md +36 -0
  11. package/docs/GPL_ADAPTERS.md +19 -0
  12. package/docs/INSTALL_BLENDER.md +39 -0
  13. package/docs/INSTALL_DASHBOARD.md +47 -0
  14. package/docs/INSTALL_PREMIERE.md +62 -0
  15. package/docs/LICENSING.md +19 -0
  16. package/docs/PREMIERE_E2E_TEST.md +55 -0
  17. package/docs/RELEASE_PROCESS.md +74 -0
  18. package/docs/ROADMAP.md +53 -0
  19. package/docs/SAFETY.md +21 -0
  20. package/docs/SECURITY.md +14 -0
  21. package/docs/TROUBLESHOOTING.md +46 -0
  22. package/docs/examples/adapter_check_report.sample.json +60 -0
  23. package/docs/examples/delivery_qc_report.sample.json +30 -0
  24. package/examples/blender-bridge-queue.mjs +42 -0
  25. package/examples/blender-e2e.mjs +53 -0
  26. package/examples/brief.txt +2 -0
  27. package/examples/minimal.gltf +23 -0
  28. package/examples/premiere-project-delivery.mjs +72 -0
  29. package/examples/premiere-qc-e2e.mjs +77 -0
  30. package/examples/sample-tool-call.json +2 -0
  31. package/package.json +77 -0
  32. package/packages/blender-gpl-adapters/LICENSE +6 -0
  33. package/packages/blender-gpl-adapters/dist/index.d.ts +10 -0
  34. package/packages/blender-gpl-adapters/dist/index.js +28 -0
  35. package/packages/blender-gpl-adapters/dist/index.js.map +1 -0
  36. package/packages/blender-gpl-adapters/package.json +15 -0
  37. package/packages/blender-gpl-adapters/src/index.ts +38 -0
  38. package/packages/blender-pro-mcp/dist/adapters/blenderBridge.d.ts +34 -0
  39. package/packages/blender-pro-mcp/dist/adapters/blenderBridge.js +79 -0
  40. package/packages/blender-pro-mcp/dist/adapters/blenderBridge.js.map +1 -0
  41. package/packages/blender-pro-mcp/dist/adapters/cli.d.ts +11 -0
  42. package/packages/blender-pro-mcp/dist/adapters/cli.js +121 -0
  43. package/packages/blender-pro-mcp/dist/adapters/cli.js.map +1 -0
  44. package/packages/blender-pro-mcp/dist/adapters/gltf.d.ts +21 -0
  45. package/packages/blender-pro-mcp/dist/adapters/gltf.js +99 -0
  46. package/packages/blender-pro-mcp/dist/adapters/gltf.js.map +1 -0
  47. package/packages/blender-pro-mcp/dist/adapters/preview.d.ts +1 -0
  48. package/packages/blender-pro-mcp/dist/adapters/preview.js +11 -0
  49. package/packages/blender-pro-mcp/dist/adapters/preview.js.map +1 -0
  50. package/packages/blender-pro-mcp/dist/index.d.ts +3 -0
  51. package/packages/blender-pro-mcp/dist/index.js +4 -0
  52. package/packages/blender-pro-mcp/dist/index.js.map +1 -0
  53. package/packages/blender-pro-mcp/dist/server.d.ts +1 -0
  54. package/packages/blender-pro-mcp/dist/server.js +4 -0
  55. package/packages/blender-pro-mcp/dist/server.js.map +1 -0
  56. package/packages/blender-pro-mcp/dist/tools/assetTools.d.ts +2 -0
  57. package/packages/blender-pro-mcp/dist/tools/assetTools.js +646 -0
  58. package/packages/blender-pro-mcp/dist/tools/assetTools.js.map +1 -0
  59. package/packages/blender-pro-mcp/dist/tools/shared.d.ts +3 -0
  60. package/packages/blender-pro-mcp/dist/tools/shared.js +94 -0
  61. package/packages/blender-pro-mcp/dist/tools/shared.js.map +1 -0
  62. package/packages/blender-pro-mcp/package.json +18 -0
  63. package/packages/blender-pro-mcp/src/adapters/blenderBridge.ts +106 -0
  64. package/packages/blender-pro-mcp/src/adapters/cli.ts +130 -0
  65. package/packages/blender-pro-mcp/src/adapters/gltf.ts +132 -0
  66. package/packages/blender-pro-mcp/src/adapters/preview.ts +11 -0
  67. package/packages/blender-pro-mcp/src/index.ts +3 -0
  68. package/packages/blender-pro-mcp/src/server.ts +4 -0
  69. package/packages/blender-pro-mcp/src/tools/assetTools.ts +663 -0
  70. package/packages/blender-pro-mcp/src/tools/shared.ts +99 -0
  71. package/packages/core/dist/approvalPolicy.d.ts +13 -0
  72. package/packages/core/dist/approvalPolicy.js +48 -0
  73. package/packages/core/dist/approvalPolicy.js.map +1 -0
  74. package/packages/core/dist/artifactStore.d.ts +14 -0
  75. package/packages/core/dist/artifactStore.js +77 -0
  76. package/packages/core/dist/artifactStore.js.map +1 -0
  77. package/packages/core/dist/coreTools.d.ts +2 -0
  78. package/packages/core/dist/coreTools.js +34 -0
  79. package/packages/core/dist/coreTools.js.map +1 -0
  80. package/packages/core/dist/index.d.ts +11 -0
  81. package/packages/core/dist/index.js +12 -0
  82. package/packages/core/dist/index.js.map +1 -0
  83. package/packages/core/dist/jobQueue.d.ts +11 -0
  84. package/packages/core/dist/jobQueue.js +14 -0
  85. package/packages/core/dist/jobQueue.js.map +1 -0
  86. package/packages/core/dist/licenseManifest.d.ts +7 -0
  87. package/packages/core/dist/licenseManifest.js +42 -0
  88. package/packages/core/dist/licenseManifest.js.map +1 -0
  89. package/packages/core/dist/mcpServer.d.ts +19 -0
  90. package/packages/core/dist/mcpServer.js +120 -0
  91. package/packages/core/dist/mcpServer.js.map +1 -0
  92. package/packages/core/dist/qcReport.d.ts +21 -0
  93. package/packages/core/dist/qcReport.js +25 -0
  94. package/packages/core/dist/qcReport.js.map +1 -0
  95. package/packages/core/dist/router.d.ts +7 -0
  96. package/packages/core/dist/router.js +55 -0
  97. package/packages/core/dist/router.js.map +1 -0
  98. package/packages/core/dist/schemaValidator.d.ts +8 -0
  99. package/packages/core/dist/schemaValidator.js +21 -0
  100. package/packages/core/dist/schemaValidator.js.map +1 -0
  101. package/packages/core/dist/server.d.ts +1 -0
  102. package/packages/core/dist/server.js +3 -0
  103. package/packages/core/dist/server.js.map +1 -0
  104. package/packages/core/dist/toolRegistry.d.ts +8 -0
  105. package/packages/core/dist/toolRegistry.js +25 -0
  106. package/packages/core/dist/toolRegistry.js.map +1 -0
  107. package/packages/core/dist/types.d.ts +58 -0
  108. package/packages/core/dist/types.js +2 -0
  109. package/packages/core/dist/types.js.map +1 -0
  110. package/packages/core/package.json +18 -0
  111. package/packages/core/src/approvalPolicy.ts +51 -0
  112. package/packages/core/src/artifactStore.ts +93 -0
  113. package/packages/core/src/coreTools.ts +36 -0
  114. package/packages/core/src/index.ts +11 -0
  115. package/packages/core/src/jobQueue.ts +22 -0
  116. package/packages/core/src/licenseManifest.ts +47 -0
  117. package/packages/core/src/mcpServer.ts +131 -0
  118. package/packages/core/src/qcReport.ts +53 -0
  119. package/packages/core/src/router.ts +61 -0
  120. package/packages/core/src/schemaValidator.ts +26 -0
  121. package/packages/core/src/server.ts +3 -0
  122. package/packages/core/src/toolRegistry.ts +31 -0
  123. package/packages/core/src/types.ts +78 -0
  124. package/packages/dashboard/dist/server.d.ts +1 -0
  125. package/packages/dashboard/dist/server.js +486 -0
  126. package/packages/dashboard/dist/server.js.map +1 -0
  127. package/packages/dashboard/package.json +18 -0
  128. package/packages/dashboard/src/server.ts +539 -0
  129. package/packages/director-agent/dist/index.d.ts +1 -0
  130. package/packages/director-agent/dist/index.js +2 -0
  131. package/packages/director-agent/dist/index.js.map +1 -0
  132. package/packages/director-agent/dist/server.d.ts +1 -0
  133. package/packages/director-agent/dist/server.js +4 -0
  134. package/packages/director-agent/dist/server.js.map +1 -0
  135. package/packages/director-agent/dist/tools.d.ts +2 -0
  136. package/packages/director-agent/dist/tools.js +115 -0
  137. package/packages/director-agent/dist/tools.js.map +1 -0
  138. package/packages/director-agent/package.json +18 -0
  139. package/packages/director-agent/src/index.ts +2 -0
  140. package/packages/director-agent/src/server.ts +4 -0
  141. package/packages/director-agent/src/tools.ts +116 -0
  142. package/packages/premiere-cep-panel/CSXS/manifest.xml +43 -0
  143. package/packages/premiere-cep-panel/README.md +21 -0
  144. package/packages/premiere-cep-panel/index.html +26 -0
  145. package/packages/premiere-cep-panel/js/main.js +107 -0
  146. package/packages/premiere-cep-panel/jsx/host.jsx +208 -0
  147. package/packages/premiere-cep-panel/package.json +8 -0
  148. package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.d.ts +21 -0
  149. package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.js +78 -0
  150. package/packages/premiere-pro-mcp/dist/adapters/ffmpegQc.js.map +1 -0
  151. package/packages/premiere-pro-mcp/dist/adapters/ffprobe.d.ts +25 -0
  152. package/packages/premiere-pro-mcp/dist/adapters/ffprobe.js +73 -0
  153. package/packages/premiere-pro-mcp/dist/adapters/ffprobe.js.map +1 -0
  154. package/packages/premiere-pro-mcp/dist/adapters/optionalTools.d.ts +11 -0
  155. package/packages/premiere-pro-mcp/dist/adapters/optionalTools.js +82 -0
  156. package/packages/premiere-pro-mcp/dist/adapters/optionalTools.js.map +1 -0
  157. package/packages/premiere-pro-mcp/dist/adapters/premiereCep.d.ts +34 -0
  158. package/packages/premiere-pro-mcp/dist/adapters/premiereCep.js +79 -0
  159. package/packages/premiere-pro-mcp/dist/adapters/premiereCep.js.map +1 -0
  160. package/packages/premiere-pro-mcp/dist/adapters/srt.d.ts +8 -0
  161. package/packages/premiere-pro-mcp/dist/adapters/srt.js +37 -0
  162. package/packages/premiere-pro-mcp/dist/adapters/srt.js.map +1 -0
  163. package/packages/premiere-pro-mcp/dist/index.d.ts +2 -0
  164. package/packages/premiere-pro-mcp/dist/index.js +3 -0
  165. package/packages/premiere-pro-mcp/dist/index.js.map +1 -0
  166. package/packages/premiere-pro-mcp/dist/server.d.ts +1 -0
  167. package/packages/premiere-pro-mcp/dist/server.js +4 -0
  168. package/packages/premiere-pro-mcp/dist/server.js.map +1 -0
  169. package/packages/premiere-pro-mcp/dist/tools/mediaTools.d.ts +2 -0
  170. package/packages/premiere-pro-mcp/dist/tools/mediaTools.js +935 -0
  171. package/packages/premiere-pro-mcp/dist/tools/mediaTools.js.map +1 -0
  172. package/packages/premiere-pro-mcp/dist/tools/shared.d.ts +3 -0
  173. package/packages/premiere-pro-mcp/dist/tools/shared.js +136 -0
  174. package/packages/premiere-pro-mcp/dist/tools/shared.js.map +1 -0
  175. package/packages/premiere-pro-mcp/package.json +18 -0
  176. package/packages/premiere-pro-mcp/src/adapters/ffmpegQc.ts +106 -0
  177. package/packages/premiere-pro-mcp/src/adapters/ffprobe.ts +105 -0
  178. package/packages/premiere-pro-mcp/src/adapters/optionalTools.ts +94 -0
  179. package/packages/premiere-pro-mcp/src/adapters/premiereCep.ts +106 -0
  180. package/packages/premiere-pro-mcp/src/adapters/srt.ts +46 -0
  181. package/packages/premiere-pro-mcp/src/index.ts +3 -0
  182. package/packages/premiere-pro-mcp/src/server.ts +4 -0
  183. package/packages/premiere-pro-mcp/src/tools/mediaTools.ts +961 -0
  184. package/packages/premiere-pro-mcp/src/tools/shared.ts +147 -0
  185. package/packages/premiere-windows-adapter/dist/index.d.ts +12 -0
  186. package/packages/premiere-windows-adapter/dist/index.js +17 -0
  187. package/packages/premiere-windows-adapter/dist/index.js.map +1 -0
  188. package/packages/premiere-windows-adapter/package.json +12 -0
  189. package/packages/premiere-windows-adapter/src/index.ts +25 -0
  190. package/scripts/blender-bridge-worker.mjs +311 -0
  191. package/scripts/check-adapters.mjs +57 -0
  192. package/scripts/install-premiere-cep.mjs +43 -0
  193. package/scripts/package-premiere-cep.mjs +236 -0
  194. package/scripts/release-assets.mjs +146 -0
  195. package/scripts/simulate-premiere-cep.mjs +194 -0
  196. package/third_party_licenses/README.md +19 -0
@@ -0,0 +1,663 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { basename, parse } from "node:path";
3
+ import type { ToolDefinition } from "@creative-pipeline-mcp/core";
4
+ import {
5
+ enqueueBlenderBridgeCommand,
6
+ findBlenderBridgeStatus,
7
+ listBlenderBridgeStatuses
8
+ } from "../adapters/blenderBridge.js";
9
+ import { optimizeWithCli, renderWithHeadlessBlender, runHeadlessBlenderScript } from "../adapters/cli.js";
10
+ import { placeholderPng } from "../adapters/preview.js";
11
+ import { artifactName, inspectAndReport, requirePath } from "./shared.js";
12
+
13
+ export const blenderTools: ToolDefinition[] = [
14
+ {
15
+ name: "blender.read_bridge_status",
16
+ description: "Read Blender bridge status JSON files produced by a trusted bridge adapter.",
17
+ category: "blender",
18
+ risk: "read",
19
+ inputSchema: {
20
+ type: "object",
21
+ properties: {},
22
+ additionalProperties: false
23
+ },
24
+ async execute() {
25
+ const statuses = await listBlenderBridgeStatuses();
26
+ return {
27
+ ok: true,
28
+ message: `${statuses.length} Blender bridge status records found`,
29
+ data: { statuses }
30
+ };
31
+ }
32
+ },
33
+ {
34
+ name: "blender.await_bridge_status",
35
+ description: "Poll Blender bridge status files until a matching command status is available.",
36
+ category: "blender",
37
+ risk: "read",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ commandId: { type: "string" },
42
+ commandType: {
43
+ type: "string",
44
+ enum: ["create_scene", "create_asset", "modify_asset", "apply_material", "run_safe_script"]
45
+ },
46
+ timeoutMs: { type: "number" },
47
+ pollIntervalMs: { type: "number" }
48
+ },
49
+ additionalProperties: false
50
+ },
51
+ async execute(_context, input) {
52
+ const timeoutMs = Math.max(0, Math.min(typeof input.timeoutMs === "number" ? input.timeoutMs : 0, 120000));
53
+ const pollIntervalMs = Math.max(100, Math.min(typeof input.pollIntervalMs === "number" ? input.pollIntervalMs : 1000, 10000));
54
+ const deadline = Date.now() + timeoutMs;
55
+ do {
56
+ const match = await findBlenderBridgeStatus({
57
+ commandId: typeof input.commandId === "string" ? input.commandId : undefined,
58
+ commandType: isBlenderBridgeCommandType(input.commandType) ? input.commandType : undefined
59
+ });
60
+ if (match) {
61
+ return {
62
+ ok: true,
63
+ message: `Blender bridge status found: ${match.status.status}`,
64
+ data: match
65
+ };
66
+ }
67
+ if (Date.now() >= deadline) {
68
+ break;
69
+ }
70
+ await sleep(pollIntervalMs);
71
+ } while (true);
72
+ return {
73
+ ok: false,
74
+ message: "Blender bridge status not found before timeout",
75
+ data: { commandId: input.commandId, commandType: input.commandType, timeoutMs }
76
+ };
77
+ }
78
+ },
79
+ {
80
+ name: "blender.create_scene",
81
+ description: "Create a scene-generation manifest for an external Blender bridge.",
82
+ category: "blender",
83
+ risk: "safe_write",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ prompt: { type: "string", maxLength: 2000 },
88
+ targetEngine: { type: "string", enum: ["Roblox", "Unity", "Unreal", "WebGL"] }
89
+ },
90
+ required: ["prompt"],
91
+ additionalProperties: false
92
+ },
93
+ async execute(context, input) {
94
+ const manifest = {
95
+ prompt: String(input.prompt ?? ""),
96
+ targetEngine: String(input.targetEngine ?? "WebGL"),
97
+ outputs: ["scene.glb", "scene_preview.png", "scene_qc_report.json"],
98
+ bridge: "queued_blender_bridge_required"
99
+ };
100
+ const artifact = await context.artifactStore.writeJson("blender/create_scene_manifest.json", manifest);
101
+ const queued = await enqueueBlenderBridgeCommand("create_scene", manifest);
102
+ return {
103
+ ok: true,
104
+ message: "Scene manifest written and Blender bridge command queued",
105
+ artifacts: [artifact, queued.path],
106
+ data: { manifest, command: queued.command }
107
+ };
108
+ }
109
+ },
110
+ {
111
+ name: "blender.apply_material",
112
+ description: "Create a material-application manifest for MaterialX or Blender material adapters.",
113
+ category: "blender",
114
+ risk: "safe_write",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: { path: { type: "string" }, material: { type: "object" } },
118
+ required: ["path"],
119
+ additionalProperties: false
120
+ },
121
+ async execute(context, input) {
122
+ const path = requirePath(input);
123
+ await context.artifactStore.assertReadableFile(path);
124
+ const manifest = { source: path, material: input.material ?? {}, adapter: "MaterialX_or_Blender_bridge" };
125
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_material_apply_manifest.json"), manifest);
126
+ const queued = await enqueueBlenderBridgeCommand("apply_material", manifest);
127
+ return {
128
+ ok: true,
129
+ message: "Material application manifest written and Blender bridge command queued",
130
+ artifacts: [artifact, queued.path],
131
+ data: { manifest, command: queued.command }
132
+ };
133
+ }
134
+ },
135
+ {
136
+ name: "blender.modify_asset",
137
+ description: "Create a non-destructive modification manifest for an existing asset.",
138
+ category: "blender",
139
+ risk: "safe_write",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: { path: { type: "string" }, instructions: { type: "string", maxLength: 4000 } },
143
+ required: ["path", "instructions"],
144
+ additionalProperties: false
145
+ },
146
+ async execute(context, input) {
147
+ const path = requirePath(input);
148
+ await context.artifactStore.assertReadableFile(path);
149
+ const manifest = {
150
+ source: path,
151
+ instructions: String(input.instructions ?? ""),
152
+ mode: "copy_then_modify",
153
+ requiredQcAfterModify: true
154
+ };
155
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_modify_manifest.json"), manifest);
156
+ const queued = await enqueueBlenderBridgeCommand("modify_asset", manifest);
157
+ return {
158
+ ok: true,
159
+ message: "Asset modification manifest written and Blender bridge command queued",
160
+ artifacts: [artifact, queued.path],
161
+ data: { manifest, command: queued.command }
162
+ };
163
+ }
164
+ },
165
+ {
166
+ name: "blender.create_asset",
167
+ description: "Alias-style manifest for creating a Blender asset through the external bridge.",
168
+ category: "blender",
169
+ risk: "safe_write",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: { prompt: { type: "string", maxLength: 2000 } },
173
+ required: ["prompt"],
174
+ additionalProperties: false
175
+ },
176
+ async execute(context, input) {
177
+ const manifest = {
178
+ prompt: String(input.prompt ?? ""),
179
+ outputs: ["asset.glb", "asset_preview.png", "asset_qc_report.json"],
180
+ bridge: "queued_blender_bridge_required"
181
+ };
182
+ const artifact = await context.artifactStore.writeJson("blender/create_asset_manifest.json", manifest);
183
+ const queued = await enqueueBlenderBridgeCommand("create_asset", manifest);
184
+ return {
185
+ ok: true,
186
+ message: "Asset creation manifest written and Blender bridge command queued",
187
+ artifacts: [artifact, queued.path],
188
+ data: { manifest, command: queued.command }
189
+ };
190
+ }
191
+ },
192
+ {
193
+ name: "blender.inspect_scene",
194
+ description: "Inspect a .glb/.gltf asset or report that external Blender is required for .blend.",
195
+ category: "blender",
196
+ risk: "read",
197
+ inputSchema: {
198
+ type: "object",
199
+ properties: { path: { type: "string" } },
200
+ required: ["path"],
201
+ additionalProperties: false
202
+ },
203
+ async execute(context, input) {
204
+ const path = requirePath(input);
205
+ await context.artifactStore.assertReadableFile(path);
206
+ if (!existsSync(path)) {
207
+ throw new Error(`Asset not found: ${path}`);
208
+ }
209
+ const report = await inspectAndReport(path);
210
+ return {
211
+ ok: report.summary.status !== "fail",
212
+ message: `Scene inspection finished: ${report.summary.status}`,
213
+ data: report as unknown as Record<string, unknown>
214
+ };
215
+ }
216
+ },
217
+ {
218
+ name: "blender.configure_engine_profile",
219
+ description: "Create a target engine profile for Roblox, Unity, Unreal, or WebGL asset budgets.",
220
+ category: "blender",
221
+ risk: "safe_write",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: { engine: { type: "string", enum: ["Roblox", "Unity", "Unreal", "WebGL"] } },
225
+ required: ["engine"],
226
+ additionalProperties: false
227
+ },
228
+ async execute(context, input) {
229
+ const engine = String(input.engine ?? "WebGL");
230
+ const profiles: Record<string, Record<string, unknown>> = {
231
+ Roblox: { maxTriangles: 10000, textureMax: 1024, formats: ["glb"] },
232
+ Unity: { maxTriangles: 50000, textureMax: 2048, formats: ["glb", "fbx"] },
233
+ Unreal: { maxTriangles: 100000, textureMax: 4096, formats: ["glb", "usd"] },
234
+ WebGL: { maxTriangles: 30000, textureMax: 2048, formats: ["glb"] }
235
+ };
236
+ const profile = profiles[engine] ?? profiles.WebGL;
237
+ const artifact = await context.artifactStore.writeJson(`blender/engine_profile_${engine}.json`, profile);
238
+ return { ok: true, message: "Engine profile written", artifacts: [artifact], data: profile };
239
+ }
240
+ },
241
+ {
242
+ name: "blender.create_usd_pipeline",
243
+ description: "Create a USD pipeline manifest for v2 asset exchange workflows.",
244
+ category: "blender",
245
+ risk: "safe_write",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: { path: { type: "string" } },
249
+ required: ["path"],
250
+ additionalProperties: false
251
+ },
252
+ async execute(context, input) {
253
+ const path = requirePath(input);
254
+ await context.artifactStore.assertReadableFile(path);
255
+ const manifest = {
256
+ source: path,
257
+ outputs: ["asset.usd", "asset_manifest.json"],
258
+ adapters: ["OpenUSD", "MaterialX", "OCIO/ACES"],
259
+ status: "external_adapter_required"
260
+ };
261
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_usd_pipeline_manifest.json"), manifest);
262
+ return { ok: true, message: "USD pipeline manifest written", artifacts: [artifact], data: manifest };
263
+ }
264
+ },
265
+ {
266
+ name: "blender.create_materialx_workflow",
267
+ description: "Create a MaterialX and OCIO/ACES lookdev workflow manifest.",
268
+ category: "blender",
269
+ risk: "safe_write",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: { path: { type: "string" }, look: { type: "string", maxLength: 200 } },
273
+ required: ["path"],
274
+ additionalProperties: false
275
+ },
276
+ async execute(context, input) {
277
+ const path = requirePath(input);
278
+ await context.artifactStore.assertReadableFile(path);
279
+ const manifest = {
280
+ source: path,
281
+ look: String(input.look ?? "neutral_pbr"),
282
+ adapters: ["MaterialX", "OpenImageIO", "OpenColorIO", "ACES"],
283
+ outputs: ["lookdev.mtlx", "color_report.json"]
284
+ };
285
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_materialx_workflow.json"), manifest);
286
+ return { ok: true, message: "MaterialX workflow manifest written", artifacts: [artifact], data: manifest };
287
+ }
288
+ },
289
+ {
290
+ name: "blender.plan_rig_animation",
291
+ description: "Create an advanced rig/animation plan without executing raw Blender scripts.",
292
+ category: "blender",
293
+ risk: "safe_write",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: { path: { type: "string" }, animationBrief: { type: "string", maxLength: 4000 } },
297
+ required: ["path"],
298
+ additionalProperties: false
299
+ },
300
+ async execute(context, input) {
301
+ const path = requirePath(input);
302
+ await context.artifactStore.assertReadableFile(path);
303
+ const plan = {
304
+ source: path,
305
+ animationBrief: String(input.animationBrief ?? ""),
306
+ steps: ["inspect_armature", "build_control_rig", "apply_animation", "export_preview", "validate_motion"],
307
+ rawBpy: "approval_required"
308
+ };
309
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_rig_animation_plan.json"), plan);
310
+ return { ok: true, message: "Rig/animation plan written", artifacts: [artifact], data: plan };
311
+ }
312
+ },
313
+ {
314
+ name: "blender.validate_asset",
315
+ description: "Write a standardized asset QC report for a .glb/.gltf/.blend target.",
316
+ category: "blender",
317
+ risk: "safe_write",
318
+ inputSchema: {
319
+ type: "object",
320
+ properties: {
321
+ path: { type: "string" },
322
+ maxTriangles: { type: "number" }
323
+ },
324
+ required: ["path"],
325
+ additionalProperties: false
326
+ },
327
+ async execute(context, input) {
328
+ const path = requirePath(input);
329
+ await context.artifactStore.assertReadableFile(path);
330
+ const maxTriangles = typeof input.maxTriangles === "number" ? input.maxTriangles : 50000;
331
+ const report = await inspectAndReport(path, maxTriangles);
332
+ const artifact = await context.artifactStore.writeJson(artifactName(path, "_asset_qc_report.json"), report);
333
+ return {
334
+ ok: report.summary.status !== "fail",
335
+ message: `Asset QC report written: ${report.summary.status}`,
336
+ artifacts: [artifact],
337
+ data: report as unknown as Record<string, unknown>
338
+ };
339
+ }
340
+ },
341
+ {
342
+ name: "blender.render_preview",
343
+ description: "Create a deterministic placeholder preview unless an external Blender renderer is configured.",
344
+ category: "blender",
345
+ risk: "safe_write",
346
+ inputSchema: {
347
+ type: "object",
348
+ properties: { path: { type: "string" } },
349
+ required: ["path"],
350
+ additionalProperties: false
351
+ },
352
+ async execute(context, input) {
353
+ const path = requirePath(input);
354
+ await context.artifactStore.assertReadableFile(path);
355
+ const artifact = await context.artifactStore.writeBytes(artifactName(path, "_preview.png"), placeholderPng());
356
+ const render = await renderWithHeadlessBlender(path, artifact);
357
+ if (render.available && !render.error) {
358
+ return {
359
+ ok: true,
360
+ message: "Headless Blender preview rendered",
361
+ artifacts: [artifact],
362
+ data: { renderer: "blender_headless", command: render.command }
363
+ };
364
+ }
365
+ return {
366
+ ok: true,
367
+ message: "Preview placeholder written; Blender headless renderer unavailable",
368
+ artifacts: [artifact],
369
+ data: { renderer: "placeholder", source: path, blender: render }
370
+ };
371
+ }
372
+ },
373
+ {
374
+ name: "blender.optimize_asset",
375
+ description: "Create an optimized artifact slot; copies source when external glTF optimizer is unavailable.",
376
+ category: "blender",
377
+ risk: "safe_write",
378
+ inputSchema: {
379
+ type: "object",
380
+ properties: { path: { type: "string" } },
381
+ required: ["path"],
382
+ additionalProperties: false
383
+ },
384
+ async execute(context, input) {
385
+ const path = requirePath(input);
386
+ await context.artifactStore.assertReadableFile(path);
387
+ const target = artifactName(path, "_optimized.glb");
388
+ const artifact = await context.artifactStore.writeBytes(target, new Uint8Array());
389
+ const optimized = await optimizeWithCli(path, artifact);
390
+ if (optimized.available && !optimized.error) {
391
+ const sourceBytes = statSync(path).size;
392
+ const optimizedBytes = statSync(artifact).size;
393
+ return {
394
+ ok: true,
395
+ message: "Optimized artifact written with external glTF optimizer",
396
+ artifacts: [artifact],
397
+ data: {
398
+ optimizer: optimized.command,
399
+ source: path,
400
+ sourceBytes,
401
+ optimizedBytes,
402
+ deltaBytes: optimizedBytes - sourceBytes,
403
+ ratio: sourceBytes > 0 ? optimizedBytes / sourceBytes : null
404
+ }
405
+ };
406
+ }
407
+ const fallback = await context.artifactStore.copyIn(path, target);
408
+ const sourceBytes = statSync(path).size;
409
+ const fallbackBytes = statSync(fallback).size;
410
+ return {
411
+ ok: true,
412
+ message: "Optimized artifact written by copy fallback; glTF optimizer unavailable or failed",
413
+ artifacts: [fallback],
414
+ data: {
415
+ optimizer: "copy_fallback",
416
+ source: path,
417
+ sourceBytes,
418
+ optimizedBytes: fallbackBytes,
419
+ deltaBytes: fallbackBytes - sourceBytes,
420
+ ratio: sourceBytes > 0 ? fallbackBytes / sourceBytes : null,
421
+ cli: optimized
422
+ }
423
+ };
424
+ }
425
+ },
426
+ {
427
+ name: "blender.export_game_ready",
428
+ description: "Export a game-ready artifact copy and QC report.",
429
+ category: "blender",
430
+ risk: "project_write",
431
+ inputSchema: {
432
+ type: "object",
433
+ properties: {
434
+ path: { type: "string" },
435
+ maxTriangles: { type: "number" }
436
+ },
437
+ required: ["path"],
438
+ additionalProperties: false
439
+ },
440
+ async execute(context, input) {
441
+ const path = requirePath(input);
442
+ await context.artifactStore.assertReadableFile(path);
443
+ const report = await inspectAndReport(path, typeof input.maxTriangles === "number" ? input.maxTriangles : 50000);
444
+ const exported = await context.artifactStore.copyIn(path, artifactName(path, "_game_ready.glb"));
445
+ const qc = await context.artifactStore.writeJson(artifactName(path, "_game_ready_qc.json"), report);
446
+ return {
447
+ ok: report.summary.status !== "fail",
448
+ message: `Game-ready export fallback completed: ${report.summary.status}`,
449
+ artifacts: [exported, qc],
450
+ data: report as unknown as Record<string, unknown>
451
+ };
452
+ }
453
+ },
454
+ {
455
+ name: "blender.create_game_asset",
456
+ description: "Create a production job manifest for an external Blender bridge to generate a game asset.",
457
+ category: "blender",
458
+ risk: "safe_write",
459
+ inputSchema: {
460
+ type: "object",
461
+ properties: {
462
+ prompt: { type: "string", maxLength: 2000 },
463
+ budget: { type: "object" }
464
+ },
465
+ required: ["prompt"],
466
+ additionalProperties: false
467
+ },
468
+ async execute(context, input) {
469
+ const prompt = String(input.prompt ?? "");
470
+ const manifest = {
471
+ prompt,
472
+ target: "game_ready_glb",
473
+ excluded: ["3D-Agent"],
474
+ requiredQc: ["triangle_budget", "origin", "scale", "normals", "textures", "export_success"],
475
+ bridge: "external_blender_required",
476
+ safeScript: "blender/create_game_asset_safe.py"
477
+ };
478
+ const artifact = await context.artifactStore.writeJson("blender/create_game_asset_job.json", manifest);
479
+ const script = await context.artifactStore.writeText(
480
+ "blender/create_game_asset_safe.py",
481
+ safeBlenderAssetScript(prompt)
482
+ );
483
+ const queued = await enqueueBlenderBridgeCommand("run_safe_script", {
484
+ ...manifest,
485
+ scriptPath: script
486
+ });
487
+ return {
488
+ ok: true,
489
+ message: "Game asset job manifest and safe Blender bridge command written",
490
+ artifacts: [artifact, script, queued.path],
491
+ data: { manifest, command: queued.command }
492
+ };
493
+ }
494
+ },
495
+ {
496
+ name: "blender.create_material_pack",
497
+ description: "Create a material-pack manifest for MaterialX/OpenImageIO/OpenColorIO-capable adapters.",
498
+ category: "blender",
499
+ risk: "safe_write",
500
+ inputSchema: {
501
+ type: "object",
502
+ properties: { prompt: { type: "string", maxLength: 2000 } },
503
+ required: ["prompt"],
504
+ additionalProperties: false
505
+ },
506
+ async execute(context, input) {
507
+ const artifact = await context.artifactStore.writeJson("blender/material_pack_manifest.json", {
508
+ prompt: String(input.prompt ?? ""),
509
+ adapters: ["MaterialX", "OpenImageIO", "OpenColorIO"],
510
+ status: "external_adapter_required"
511
+ });
512
+ return { ok: true, message: "Material pack manifest written", artifacts: [artifact] };
513
+ }
514
+ },
515
+ {
516
+ name: "blender.fix_asset_issues",
517
+ description: "Create a repair plan from an asset QC report.",
518
+ category: "blender",
519
+ risk: "safe_write",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: { path: { type: "string" } },
523
+ required: ["path"],
524
+ additionalProperties: false
525
+ },
526
+ async execute(context, input) {
527
+ const path = requirePath(input);
528
+ await context.artifactStore.assertReadableFile(path);
529
+ const report = await inspectAndReport(path);
530
+ const plan = {
531
+ source: basename(path),
532
+ fixes: report.checks
533
+ .filter((check) => check.status === "warn" || check.status === "fail")
534
+ .map((check) => ({ check: check.id, action: `route_to_adapter_for_${check.id.replaceAll(".", "_")}` }))
535
+ };
536
+ const artifact = await context.artifactStore.writeJson(
537
+ `blender/${parse(basename(path)).name}_repair_plan.json`,
538
+ plan
539
+ );
540
+ return { ok: true, message: "Asset repair plan written", artifacts: [artifact], data: plan };
541
+ }
542
+ },
543
+ {
544
+ name: "blender.repair_basic_asset",
545
+ description: "Run a template-based Blender repair pass for scale, normals, triangulation, and GLB export.",
546
+ category: "blender",
547
+ risk: "safe_write",
548
+ inputSchema: {
549
+ type: "object",
550
+ properties: {
551
+ path: { type: "string" },
552
+ maxTriangles: { type: "number" }
553
+ },
554
+ required: ["path"],
555
+ additionalProperties: false
556
+ },
557
+ async execute(context, input) {
558
+ const path = requirePath(input);
559
+ await context.artifactStore.assertReadableFile(path);
560
+ const repairedTarget = artifactName(path, "_repaired.glb");
561
+ const repaired = `${context.artifactStore.root}/${repairedTarget}`;
562
+ const scriptText = repairBasicAssetScript(path, repaired);
563
+ const script = await context.artifactStore.writeText(artifactName(path, "_repair_basic.py"), scriptText);
564
+ const run = await runHeadlessBlenderScript(scriptText);
565
+ if (!run.available || run.error) {
566
+ return {
567
+ ok: false,
568
+ message: "Basic repair script written; headless Blender repair was not completed",
569
+ artifacts: [script],
570
+ data: { blender: run }
571
+ };
572
+ }
573
+ if (!existsSync(repaired)) {
574
+ return {
575
+ ok: false,
576
+ message: "Basic repair script ran but did not produce a repaired GLB",
577
+ artifacts: [script],
578
+ data: { blender: run, expectedOutput: repaired }
579
+ };
580
+ }
581
+ const report = await inspectAndReport(repaired, typeof input.maxTriangles === "number" ? input.maxTriangles : 50000);
582
+ const qc = await context.artifactStore.writeJson(artifactName(path, "_repaired_qc.json"), report);
583
+ return {
584
+ ok: report.summary.status !== "fail",
585
+ message: `Basic Blender repair completed: ${report.summary.status}`,
586
+ artifacts: [script, repaired, qc],
587
+ data: { blender: run, qc: report }
588
+ };
589
+ }
590
+ }
591
+ ];
592
+
593
+ function safeBlenderAssetScript(prompt: string): string {
594
+ const name = prompt
595
+ .replace(/[^a-z0-9]+/giu, "_")
596
+ .replace(/^_+|_+$/gu, "")
597
+ .slice(0, 48) || "CreativePipelineAsset";
598
+ return `import bpy
599
+
600
+ bpy.ops.object.select_all(action="SELECT")
601
+ bpy.ops.object.delete()
602
+ bpy.ops.mesh.primitive_cube_add(size=1)
603
+ asset = bpy.context.object
604
+ asset.name = ${JSON.stringify(name)}
605
+ asset.location = (0, 0, 0)
606
+
607
+ mat = bpy.data.materials.new(name=${JSON.stringify(`${name}_Material`)})
608
+ mat.use_nodes = True
609
+ asset.data.materials.append(mat)
610
+
611
+ bpy.ops.export_scene.gltf(filepath=${JSON.stringify(`${name}.glb`)}, export_format="GLB")
612
+ `;
613
+ }
614
+
615
+ function repairBasicAssetScript(source: string, target: string): string {
616
+ return `import bpy
617
+
618
+ source = ${JSON.stringify(source)}
619
+ target = ${JSON.stringify(target)}
620
+
621
+ bpy.ops.object.select_all(action="SELECT")
622
+ bpy.ops.object.delete()
623
+
624
+ if source.lower().endswith((".glb", ".gltf")):
625
+ bpy.ops.import_scene.gltf(filepath=source)
626
+ else:
627
+ raise RuntimeError("repair_basic_asset supports .glb and .gltf inputs")
628
+
629
+ for obj in list(bpy.context.scene.objects):
630
+ if obj.type != "MESH":
631
+ continue
632
+ bpy.ops.object.select_all(action="DESELECT")
633
+ obj.select_set(True)
634
+ bpy.context.view_layer.objects.active = obj
635
+ bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
636
+ try:
637
+ bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="BOUNDS")
638
+ except Exception:
639
+ pass
640
+ bpy.ops.object.mode_set(mode="EDIT")
641
+ bpy.ops.mesh.select_all(action="SELECT")
642
+ bpy.ops.mesh.normals_make_consistent(inside=False)
643
+ bpy.ops.object.mode_set(mode="OBJECT")
644
+ modifier = obj.modifiers.new(name="CreativePipelineTriangulate", type="TRIANGULATE")
645
+ bpy.ops.object.modifier_apply(modifier=modifier.name)
646
+
647
+ bpy.ops.export_scene.gltf(filepath=target, export_format="GLB")
648
+ `;
649
+ }
650
+
651
+ function sleep(ms: number): Promise<void> {
652
+ return new Promise((resolve) => setTimeout(resolve, ms));
653
+ }
654
+
655
+ function isBlenderBridgeCommandType(
656
+ value: unknown
657
+ ): value is "create_scene" | "create_asset" | "modify_asset" | "apply_material" | "run_safe_script" {
658
+ return value === "create_scene"
659
+ || value === "create_asset"
660
+ || value === "modify_asset"
661
+ || value === "apply_material"
662
+ || value === "run_safe_script";
663
+ }