summer-engine 2.3.0 → 2.6.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 (237) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +24 -8
  3. package/.codex-plugin/plugin.json +19 -8
  4. package/.cursor-plugin/plugin.json +18 -7
  5. package/.factory-plugin/plugin.json +3 -3
  6. package/.opencode/INSTALL.md +1 -1
  7. package/AGENTS.md +6 -4
  8. package/CHANGELOG.md +46 -7
  9. package/CLAUDE.md +2 -2
  10. package/GEMINI.md +5 -6
  11. package/README.md +116 -109
  12. package/_persona/summer/SKILL.md +5 -6
  13. package/commands/gameskill.md +103 -0
  14. package/dist/bin/summer.js +17 -4
  15. package/dist/commands/cloud.d.ts +2 -0
  16. package/dist/commands/cloud.js +80 -0
  17. package/dist/commands/create.js +2 -0
  18. package/dist/commands/doctor.js +90 -1
  19. package/dist/commands/install.d.ts +17 -0
  20. package/dist/commands/install.js +63 -4
  21. package/dist/commands/login.js +4 -1
  22. package/dist/commands/logout.js +1 -1
  23. package/dist/commands/memory.d.ts +14 -0
  24. package/dist/commands/memory.js +196 -0
  25. package/dist/commands/open.js +1 -1
  26. package/dist/commands/plan.d.ts +2 -0
  27. package/dist/commands/plan.js +60 -0
  28. package/dist/commands/run.js +132 -35
  29. package/dist/commands/setup.js +8 -2
  30. package/dist/commands/skills.js +90 -6
  31. package/dist/commands/status.js +3 -0
  32. package/dist/lib/agent-config.d.ts +2 -2
  33. package/dist/lib/agent-config.js +134 -7
  34. package/dist/lib/api-client.d.ts +13 -0
  35. package/dist/lib/api-client.js +63 -26
  36. package/dist/lib/async-op-lifecycle.d.ts +60 -0
  37. package/dist/lib/async-op-lifecycle.js +83 -0
  38. package/dist/lib/auth.d.ts +2 -0
  39. package/dist/lib/auth.js +13 -0
  40. package/dist/lib/cloud/api.d.ts +79 -0
  41. package/dist/lib/cloud/api.js +107 -0
  42. package/dist/lib/cloud/apply.d.ts +32 -0
  43. package/dist/lib/cloud/apply.js +294 -0
  44. package/dist/lib/cloud/checkpoint.d.ts +26 -0
  45. package/dist/lib/cloud/checkpoint.js +142 -0
  46. package/dist/lib/cloud/conflicts.d.ts +55 -0
  47. package/dist/lib/cloud/conflicts.js +131 -0
  48. package/dist/lib/cloud/containment.d.ts +8 -0
  49. package/dist/lib/cloud/containment.js +52 -0
  50. package/dist/lib/cloud/diff.d.ts +39 -0
  51. package/dist/lib/cloud/diff.js +138 -0
  52. package/dist/lib/cloud/engine-bridge.d.ts +21 -0
  53. package/dist/lib/cloud/engine-bridge.js +105 -0
  54. package/dist/lib/cloud/hash-cache.d.ts +23 -0
  55. package/dist/lib/cloud/hash-cache.js +44 -0
  56. package/dist/lib/cloud/hash.d.ts +10 -0
  57. package/dist/lib/cloud/hash.js +111 -0
  58. package/dist/lib/cloud/http.d.ts +17 -0
  59. package/dist/lib/cloud/http.js +47 -0
  60. package/dist/lib/cloud/lock-path.d.ts +1 -0
  61. package/dist/lib/cloud/lock-path.js +5 -0
  62. package/dist/lib/cloud/lock.d.ts +14 -0
  63. package/dist/lib/cloud/lock.js +143 -0
  64. package/dist/lib/cloud/manifest.d.ts +6 -0
  65. package/dist/lib/cloud/manifest.js +41 -0
  66. package/dist/lib/cloud/paths.d.ts +20 -0
  67. package/dist/lib/cloud/paths.js +90 -0
  68. package/dist/lib/cloud/project-godot-merge.d.ts +32 -0
  69. package/dist/lib/cloud/project-godot-merge.js +185 -0
  70. package/dist/lib/cloud/rules.d.ts +39 -0
  71. package/dist/lib/cloud/rules.js +159 -0
  72. package/dist/lib/cloud/sync.d.ts +21 -0
  73. package/dist/lib/cloud/sync.js +518 -0
  74. package/dist/lib/cloud/transfer.d.ts +23 -0
  75. package/dist/lib/cloud/transfer.js +184 -0
  76. package/dist/lib/cloud/types.d.ts +62 -0
  77. package/dist/lib/cloud/validate-path.d.ts +23 -0
  78. package/dist/lib/cloud/validate-path.js +103 -0
  79. package/dist/lib/engine.d.ts +6 -0
  80. package/dist/lib/engine.js +36 -1
  81. package/dist/lib/game-task-plan.d.ts +42 -0
  82. package/dist/lib/game-task-plan.js +376 -0
  83. package/dist/lib/project-memory.d.ts +33 -0
  84. package/dist/lib/project-memory.js +220 -0
  85. package/dist/lib/skills-registry.d.ts +134 -61
  86. package/dist/lib/skills-registry.js +102 -4
  87. package/dist/lib/version-check.d.ts +106 -0
  88. package/dist/lib/version-check.js +338 -0
  89. package/dist/mcp/server.d.ts +2 -0
  90. package/dist/mcp/server.js +83 -0
  91. package/dist/mcp/tools/asset-tools.js +265 -45
  92. package/dist/mcp/tools/cloud-tools.d.ts +2 -0
  93. package/dist/mcp/tools/cloud-tools.js +42 -0
  94. package/dist/mcp/tools/debug-tools.js +32 -2
  95. package/dist/mcp/tools/generate-tools.js +101 -13
  96. package/dist/mcp/tools/project-tools.js +76 -21
  97. package/dist/mcp/tools/with-engine.d.ts +14 -0
  98. package/dist/mcp/tools/with-engine.js +52 -7
  99. package/gemini-extension.json +3 -3
  100. package/hooks/pre-commit-doctor.sh +0 -0
  101. package/hooks/run-hook.cmd +0 -0
  102. package/hooks/session-start.sh +0 -0
  103. package/package.json +20 -7
  104. package/skills/2d-assets/character-portrait/SKILL.md +1 -1
  105. package/skills/2d-assets/concept-art/SKILL.md +1 -1
  106. package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
  107. package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
  108. package/skills/2d-assets/pixel-art/SKILL.md +1 -1
  109. package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
  110. package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
  111. package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
  112. package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
  113. package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
  114. package/skills/3d-assets/character-model/SKILL.md +28 -6
  115. package/skills/3d-assets/environment-kit/SKILL.md +7 -2
  116. package/skills/3d-assets/organic-model/SKILL.md +5 -4
  117. package/skills/3d-assets/prop-model/SKILL.md +17 -9
  118. package/skills/3d-assets/vehicle-model/SKILL.md +5 -4
  119. package/skills/README.md +37 -30
  120. package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
  121. package/skills/animation/animation-tree/SKILL.md +4 -2
  122. package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
  123. package/skills/animation/generate-motion/SKILL.md +26 -5
  124. package/skills/animation/procedural-animation/SKILL.md +2 -2
  125. package/skills/asset-pipeline/asset-strategy/SKILL.md +3 -2
  126. package/skills/audio/adaptive-music/SKILL.md +1 -1
  127. package/skills/audio/audio-direction/SKILL.md +5 -5
  128. package/skills/audio/sound-effect/SKILL.md +2 -2
  129. package/skills/audio/voice-line/SKILL.md +25 -3
  130. package/skills/character-controllers/fps-controller/SKILL.md +5 -5
  131. package/skills/debugging/debug/SKILL.md +39 -1
  132. package/skills/deployment/export-and-ship/SKILL.md +4 -4
  133. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
  134. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
  135. package/skills/level-design/design-level/SKILL.md +5 -5
  136. package/skills/level-design/scene-to-level/SKILL.md +125 -0
  137. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
  138. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
  139. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
  140. package/skills/performance/tune-performance/SKILL.md +5 -5
  141. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  142. package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
  143. package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
  144. package/skills/scene-and-project/play/SKILL.md +1 -1
  145. package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
  146. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
  147. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  148. package/skills/video/animated-loop/SKILL.md +1 -1
  149. package/skills/video/cinematic-cutscene/SKILL.md +1 -1
  150. package/skills/video/trailer-shot/SKILL.md +1 -1
  151. package/skills/visual-effects/game-feel/SKILL.md +6 -6
  152. package/skills/workflow/brainstorming/SKILL.md +164 -0
  153. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
  154. package/skills/workflow/brainstorming/scripts/helper.js +88 -0
  155. package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
  156. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
  157. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
  158. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
  159. package/skills/workflow/brainstorming/visual-companion.md +287 -0
  160. package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
  161. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
  162. package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
  163. package/skills/workflow/gameskill/SKILL.md +111 -0
  164. package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
  165. package/skills/workflow/investigating-bugs/SKILL.md +296 -0
  166. package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
  167. package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
  168. package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
  169. package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
  170. package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
  171. package/skills/workflow/investigating-bugs/test-academic.md +14 -0
  172. package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
  173. package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
  174. package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
  175. package/skills/workflow/mcpupdate/SKILL.md +134 -0
  176. package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
  177. package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
  178. package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
  179. package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
  180. package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
  181. package/skills/workflow/verification-before-completion/SKILL.md +139 -0
  182. package/skills/workflow/writing-plans/SKILL.md +152 -0
  183. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
  184. package/skills/workflow/writing-skills/SKILL.md +655 -0
  185. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  186. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  187. package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  188. package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  189. package/skills/workflow/writing-skills/render-graphs.js +168 -0
  190. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  191. package/dist/commands/assets.d.ts +0 -2
  192. package/dist/commands/assets.js +0 -189
  193. package/dist/commands/debug.d.ts +0 -2
  194. package/dist/commands/debug.js +0 -127
  195. package/dist/commands/doctor.test.js +0 -29
  196. package/dist/commands/project.d.ts +0 -2
  197. package/dist/commands/project.js +0 -170
  198. package/dist/commands/scene.d.ts +0 -2
  199. package/dist/commands/scene.js +0 -290
  200. package/dist/lib/agent-config.test.d.ts +0 -1
  201. package/dist/lib/agent-config.test.js +0 -80
  202. package/dist/lib/format.test.d.ts +0 -1
  203. package/dist/lib/format.test.js +0 -36
  204. package/dist/lib/plugin-manifests.test.d.ts +0 -1
  205. package/dist/lib/plugin-manifests.test.js +0 -134
  206. package/dist/lib/with-engine.d.ts +0 -14
  207. package/dist/lib/with-engine.js +0 -109
  208. package/skills/_shared/collaborative-protocol.md +0 -50
  209. package/skills/_shared/gd-style.md +0 -121
  210. package/skills/_shared/godot-version.md +0 -32
  211. package/skills/_shared/mcp-tools-reference.md +0 -133
  212. package/skills/_shared/summer-folder.md +0 -44
  213. package/skills/_shared/template-registry.md +0 -40
  214. package/skills/_tests/runner.md +0 -90
  215. package/skills/_tests/specs/3d-lighting.md +0 -19
  216. package/skills/_tests/specs/art-direction.md +0 -115
  217. package/skills/_tests/specs/asset-strategy.md +0 -19
  218. package/skills/_tests/specs/audio-direction.md +0 -123
  219. package/skills/_tests/specs/brainstorm-game.md +0 -99
  220. package/skills/_tests/specs/debug.md +0 -94
  221. package/skills/_tests/specs/design-level.md +0 -113
  222. package/skills/_tests/specs/design-mechanic.md +0 -112
  223. package/skills/_tests/specs/design-npc.md +0 -143
  224. package/skills/_tests/specs/export-and-ship.md +0 -114
  225. package/skills/_tests/specs/fps-controller.md +0 -113
  226. package/skills/_tests/specs/gdscript-patterns.md +0 -19
  227. package/skills/_tests/specs/host-authoritative-state.md +0 -104
  228. package/skills/_tests/specs/make-game.md +0 -19
  229. package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
  230. package/skills/_tests/specs/play.md +0 -88
  231. package/skills/_tests/specs/scene-composition.md +0 -19
  232. package/skills/_tests/specs/setup-multiplayer.md +0 -106
  233. package/skills/_tests/specs/tune-performance.md +0 -104
  234. package/skills/_tests/specs/ui-basics.md +0 -19
  235. package/skills/_tests/specs/vfx.md +0 -116
  236. package/skills/catalog.yaml +0 -991
  237. /package/dist/{commands/doctor.test.d.ts → lib/cloud/types.js} +0 -0
@@ -86,6 +86,143 @@ async function searchAssetsApi(params) {
86
86
  }
87
87
  return data;
88
88
  }
89
+ async function authedGetJson(endpoint, timeoutMs = 15_000) {
90
+ const token = await getAuthToken();
91
+ if (!token) {
92
+ return {
93
+ status: 401,
94
+ error: "not_logged_in",
95
+ message: "Not signed in. Run in your terminal:\n npx summer-engine login",
96
+ };
97
+ }
98
+ const res = await fetch(`${GATEWAY_URL}${endpoint}`, {
99
+ headers: { Authorization: `Bearer ${token}` },
100
+ signal: AbortSignal.timeout(timeoutMs),
101
+ });
102
+ const data = (await res.json());
103
+ if (!res.ok) {
104
+ return {
105
+ status: res.status,
106
+ data,
107
+ error: data?.error || "request_failed",
108
+ message: data?.message || "Request failed",
109
+ };
110
+ }
111
+ return { status: res.status, data };
112
+ }
113
+ async function getAssetApi(assetId) {
114
+ const result = await authedGetJson(`/api/mcp/assets/${encodeURIComponent(assetId)}`);
115
+ if (result.error) {
116
+ return { error: result.error, message: result.message };
117
+ }
118
+ return { asset: result.data?.asset };
119
+ }
120
+ async function getAssetDownloadUrlApi(assetId, role) {
121
+ const result = await authedGetJson(`/api/mcp/assets/${encodeURIComponent(assetId)}/download-url?role=${encodeURIComponent(role)}`);
122
+ if (result.error) {
123
+ return { error: result.error, message: result.message };
124
+ }
125
+ return { data: result.data };
126
+ }
127
+ function fileNameFromUrl(fileUrl) {
128
+ return fileUrl.split("/").pop()?.split("?")[0] || "asset";
129
+ }
130
+ function sanitizeNodeName(name) {
131
+ return ((name || "Asset")
132
+ .replace(/[^\p{L}\p{N}_]+/gu, "_")
133
+ .replace(/^_+|_+$/g, "") || "Asset");
134
+ }
135
+ async function buildImportEntriesForAsset(asset, targetPath) {
136
+ const fileUrl = asset.importUrl || asset.fileUrl;
137
+ if (!fileUrl)
138
+ throw new Error("Asset has no import URL.");
139
+ const fileName = targetPath?.split("/").pop() || asset.fileName || fileNameFromUrl(fileUrl);
140
+ const packSlug = asset.packSlug ?? getPackSlugFromUrl(fileUrl) ?? "misc";
141
+ if (targetPath) {
142
+ if (asset.type === "3d_model" && fileUrl.includes("kenney/3d/")) {
143
+ const textureUrl = buildKenneyTextureUrl(fileUrl);
144
+ const hasTexture = await textureExists(textureUrl);
145
+ if (hasTexture) {
146
+ const glbDir = targetPath.replace(/\/[^/]+$/, "");
147
+ return {
148
+ importPath: targetPath,
149
+ imports: [
150
+ { url: textureUrl, path: `${glbDir}/Textures/colormap.png` },
151
+ { url: fileUrl, path: targetPath },
152
+ ],
153
+ };
154
+ }
155
+ }
156
+ return {
157
+ importPath: targetPath,
158
+ imports: [{ url: fileUrl, path: targetPath }],
159
+ };
160
+ }
161
+ if (asset.type === "3d_model" && fileUrl.includes("kenney/3d/")) {
162
+ const imports = await buildKenneyImportEntries(fileUrl, packSlug, fileName);
163
+ return { imports, importPath: imports[imports.length - 1].path };
164
+ }
165
+ const path = asset.type === "3d_model"
166
+ ? `res://assets/models/${fileName}`
167
+ : `res://assets/${fileName}`;
168
+ return { imports: [{ url: fileUrl, path }], importPath: path };
169
+ }
170
+ async function importResolvedAsset(args) {
171
+ const { asset, parent, path, name } = args;
172
+ const { imports, importPath } = await buildImportEntriesForAsset(asset, path);
173
+ const client = await getClient();
174
+ const importResult = imports.length === 1
175
+ ? await client.executeOps([
176
+ { op: "ImportFromUrl", url: imports[0].url, path: imports[0].path },
177
+ ])
178
+ : await client.executeOps([{ op: "ImportFromUrlBatch", imports }]);
179
+ const ok = importResult?.results?.[0]?.ok;
180
+ if (!ok) {
181
+ throw new Error("Could not import asset. Check engine logs.");
182
+ }
183
+ let addedToScene = false;
184
+ if (parent && asset.type === "3d_model") {
185
+ await client.executeOps([
186
+ {
187
+ op: "InstantiateScene",
188
+ parent,
189
+ scene: importPath,
190
+ name: sanitizeNodeName(name || asset.title),
191
+ },
192
+ ]);
193
+ addedToScene = true;
194
+ }
195
+ return {
196
+ success: true,
197
+ assetId: asset.id,
198
+ asset: asset.title,
199
+ type: asset.type,
200
+ importedTo: importPath,
201
+ addedToScene,
202
+ parent: parent || null,
203
+ };
204
+ }
205
+ function jsonSuccess(data) {
206
+ return {
207
+ content: [
208
+ {
209
+ type: "text",
210
+ text: JSON.stringify(data, null, 2),
211
+ },
212
+ ],
213
+ };
214
+ }
215
+ function jsonError(data) {
216
+ return {
217
+ content: [
218
+ {
219
+ type: "text",
220
+ text: JSON.stringify(data, null, 2),
221
+ },
222
+ ],
223
+ isError: true,
224
+ };
225
+ }
89
226
  export function registerAssetTools(server) {
90
227
  server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem. **Free for all users** with rate limits.
91
228
 
@@ -129,6 +266,124 @@ Requires authentication (so we can attribute usage and apply per-user rate limit
129
266
  ],
130
267
  };
131
268
  });
269
+ server.tool("summer_list_my_assets", `List or search the signed-in user's generated and uploaded assets.
270
+
271
+ Use this after generation jobs complete, when the user says "the model I made",
272
+ "my last character", or when you need to retrieve an asset before importing it
273
+ into the local project.
274
+
275
+ Returns exact asset IDs plus file/import URLs. Use summer_get_asset for full
276
+ metadata or summer_import_asset_by_id to import a specific result.`, {
277
+ query: z
278
+ .string()
279
+ .default("")
280
+ .describe("Optional search term. Empty string lists recent assets."),
281
+ assetType: z
282
+ .enum(["2d_image", "animation", "3d_model", "audio", "music", "all"])
283
+ .default("all")
284
+ .describe("Filter by asset type"),
285
+ limit: z.number().default(10).describe("Max results (1-20)"),
286
+ }, async ({ query, assetType, limit }) => {
287
+ const result = await searchAssetsApi({
288
+ query,
289
+ assetType,
290
+ limit: Math.min(limit, 20),
291
+ source: "my_assets",
292
+ });
293
+ if (result.error) {
294
+ return jsonError({ error: result.error, message: result.message });
295
+ }
296
+ return jsonSuccess({
297
+ assets: result.assets,
298
+ count: result.count,
299
+ message: result.message,
300
+ });
301
+ });
302
+ server.tool("summer_get_asset", `Fetch one asset by exact Summer asset ID.
303
+
304
+ Use this after a generation job returns assetId/rigAssetId/animationAssetId, or
305
+ after summer_list_my_assets/search results when you need the stable file URL,
306
+ download URL, viewer URL, metadata, license, visibility, parent chain, or
307
+ provider details.`, {
308
+ assetId: z.string().describe("Summer ArtAssets id"),
309
+ }, async ({ assetId }) => {
310
+ const result = await getAssetApi(assetId);
311
+ if (result.error || !result.asset) {
312
+ return jsonError({
313
+ error: result.error || "asset_not_found",
314
+ message: result.message || "Asset not found.",
315
+ });
316
+ }
317
+ return jsonSuccess({ asset: result.asset });
318
+ });
319
+ server.tool("summer_get_asset_download_url", `Get a downloadable URL for a specific asset file.
320
+
321
+ Today this usually returns the Cloudinary URL plus Summer's download proxy.
322
+ The response shape is future-proofed for signed URLs, so prefer this tool over
323
+ handing users raw fileUrl when they explicitly ask to download.`, {
324
+ assetId: z.string().describe("Summer ArtAssets id"),
325
+ role: z
326
+ .enum(["primary", "thumbnail"])
327
+ .default("primary")
328
+ .describe("Which file to download"),
329
+ }, async ({ assetId, role }) => {
330
+ const result = await getAssetDownloadUrlApi(assetId, role);
331
+ if (result.error) {
332
+ return jsonError({ error: result.error, message: result.message });
333
+ }
334
+ return jsonSuccess(result.data);
335
+ });
336
+ server.tool("summer_import_asset_by_id", `Import an exact Summer asset ID into the current project.
337
+
338
+ Use this when the asset was just generated, selected from my assets, or returned
339
+ by search. Unlike summer_import_asset, this does not search or guess: it fetches
340
+ the asset by ID, downloads it through Summer Engine, and runs Godot's import
341
+ pipeline. For 3D models, pass parent to instantiate it in the open scene after
342
+ import.`, {
343
+ assetId: z.string().describe("Summer ArtAssets id"),
344
+ path: z
345
+ .string()
346
+ .optional()
347
+ .describe("Optional target res:// path. If omitted, a path is inferred from asset type and filename."),
348
+ parent: z
349
+ .string()
350
+ .optional()
351
+ .describe("Optional parent node path. For 3D models only; imports and instantiates under this node."),
352
+ name: z
353
+ .string()
354
+ .optional()
355
+ .describe("Optional scene node name when parent is provided."),
356
+ }, async ({ assetId, path, parent, name }) => {
357
+ const fetched = await getAssetApi(assetId);
358
+ if (fetched.error || !fetched.asset) {
359
+ return jsonError({
360
+ error: fetched.error || "asset_not_found",
361
+ message: fetched.message || "Asset not found.",
362
+ });
363
+ }
364
+ try {
365
+ const imported = await importResolvedAsset({
366
+ asset: fetched.asset,
367
+ parent,
368
+ path,
369
+ name,
370
+ });
371
+ return jsonSuccess({
372
+ ...imported,
373
+ message: imported.addedToScene
374
+ ? `Imported "${fetched.asset.title}" and added it to ${parent}.`
375
+ : `Imported "${fetched.asset.title}" to ${imported.importedTo}.`,
376
+ });
377
+ }
378
+ catch (err) {
379
+ const msg = err instanceof Error ? err.message : String(err);
380
+ return jsonError({
381
+ error: "import_failed",
382
+ message: msg,
383
+ hint: "Make sure Summer Engine is running and the target scene is open.",
384
+ });
385
+ }
386
+ });
132
387
  server.tool("summer_import_asset", `Search the asset library and import the best match into the project in one step.
133
388
 
134
389
  Use when the user wants a specific type of asset added: "Add a tree to the scene", "Import a wooden barrel".
@@ -138,8 +393,9 @@ Requires authentication. If the user gets an auth error, they need to run 'npx s
138
393
  query: z.string().describe("What to find, e.g. 'low-poly tree', 'wooden crate'"),
139
394
  parent: z.string().optional().describe("Parent node path to add the asset under, e.g. './World'. If omitted, only imports (no scene placement)"),
140
395
  assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("3d_model").describe("Preferred asset type"),
141
- }, async ({ query, parent, assetType }) => {
142
- const searchResult = await searchAssetsApi({ query, assetType, limit: 5 });
396
+ source: z.enum(["library", "my_assets", "all"]).default("all").describe("Where to search before importing"),
397
+ }, async ({ query, parent, assetType, source }) => {
398
+ const searchResult = await searchAssetsApi({ query, assetType, limit: 5, source });
143
399
  if (searchResult.error) {
144
400
  return {
145
401
  content: [
@@ -176,45 +432,8 @@ Requires authentication. If the user gets an auth error, they need to run 'npx s
176
432
  isError: true,
177
433
  };
178
434
  }
179
- const fileName = fileUrl.split("/").pop()?.split("?")[0] || "asset";
180
- const packSlug = best.packSlug ?? getPackSlugFromUrl(fileUrl) ?? "misc";
181
- let imports;
182
- if (best.type === "3d_model" && fileUrl.includes("kenney/3d/")) {
183
- imports = await buildKenneyImportEntries(fileUrl, packSlug, fileName);
184
- }
185
- else {
186
- const path = best.type === "3d_model"
187
- ? `res://assets/models/${fileName}`
188
- : `res://assets/${fileName}`;
189
- imports = [{ url: fileUrl, path }];
190
- }
191
- const importPath = imports[imports.length - 1].path;
192
435
  try {
193
- const client = await getClient();
194
- const importResult = imports.length === 1
195
- ? await client.executeOps([{ op: "ImportFromUrl", url: imports[0].url, path: imports[0].path }])
196
- : await client.executeOps([{ op: "ImportFromUrlBatch", imports }]);
197
- const ok = importResult?.results?.[0]?.ok;
198
- if (!ok) {
199
- return {
200
- content: [
201
- {
202
- type: "text",
203
- text: JSON.stringify({
204
- error: "Import failed",
205
- message: "Could not import asset. Check engine logs.",
206
- asset: best.title,
207
- }, null, 2),
208
- },
209
- ],
210
- isError: true,
211
- };
212
- }
213
- if (parent && best.type === "3d_model") {
214
- await client.executeOps([
215
- { op: "InstantiateScene", parent, scene: importPath, name: best.title.replace(/\s+/g, "_") },
216
- ]);
217
- }
436
+ const imported = await importResolvedAsset({ asset: best, parent });
218
437
  // 2D sprites and audio: import only; user adds to scene manually
219
438
  return {
220
439
  content: [
@@ -222,14 +441,15 @@ Requires authentication. If the user gets an auth error, they need to run 'npx s
222
441
  type: "text",
223
442
  text: JSON.stringify({
224
443
  success: true,
444
+ assetId: best.id,
225
445
  asset: best.title,
226
446
  type: best.type,
227
- importedTo: importPath,
228
- addedToScene: parent ? true : false,
229
- parent: parent || null,
230
- message: parent
447
+ importedTo: imported.importedTo,
448
+ addedToScene: imported.addedToScene,
449
+ parent: imported.parent,
450
+ message: imported.addedToScene
231
451
  ? `Imported "${best.title}" and added to ${parent}`
232
- : `Imported "${best.title}" to ${importPath}`,
452
+ : `Imported "${best.title}" to ${imported.importedTo}`,
233
453
  }, null, 2),
234
454
  },
235
455
  ],
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCloudTools(server: McpServer): void;
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { cloudCheckpoints, cloudConflicts, cloudInit, cloudPull, cloudPush, cloudRestore, cloudStatus, } from "../../lib/cloud/sync.js";
3
+ const projectArg = z.object({
4
+ project: z.string().optional().describe("Project root path. Defaults to the current working directory."),
5
+ });
6
+ const bootstrap = z.enum(["keep-cloud", "keep-local", "merge"]);
7
+ function textJson(value) {
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(value, null, 2),
13
+ },
14
+ ],
15
+ };
16
+ }
17
+ export function registerCloudTools(server) {
18
+ server.tool("summer_cloud_init", "Enable Summer Cloud for a project.", projectArg.shape, async (args) => textJson(await cloudInit({ project: args.project, face: "mcp" })));
19
+ server.tool("summer_cloud_status", "Show Summer Cloud sync status.", projectArg.shape, async (args) => textJson(await cloudStatus({ project: args.project, face: "mcp" })));
20
+ server.tool("summer_cloud_push", "Push local project changes to Summer Cloud.", {
21
+ ...projectArg.shape,
22
+ confirmDeletes: z.boolean().default(false).describe("Required when a push would delete many cloud files."),
23
+ bootstrap: bootstrap.optional().describe("No-base bootstrap choice."),
24
+ adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path."),
25
+ }, async (args) => textJson(await cloudPush({ ...args, face: "mcp" })));
26
+ server.tool("summer_cloud_pull", "Pull Summer Cloud changes into the local project.", {
27
+ ...projectArg.shape,
28
+ bootstrap: bootstrap.optional().describe("No-base bootstrap choice."),
29
+ adoptPath: z.boolean().optional().describe("Accept that the project folder moved and update the recorded path."),
30
+ }, async (args) => textJson(await cloudPull({ ...args, face: "mcp" })));
31
+ server.tool("summer_cloud_restore", "Restore a retained cloud version (creates a new head version server-side, then pulls), or a local pre-sync checkpoint via checkpointStamp.", {
32
+ ...projectArg.shape,
33
+ version: z.number().int().positive().optional().describe("Cloud version to restore."),
34
+ checkpointStamp: z.string().optional().describe("Local checkpoint stamp (see summer_cloud_checkpoints)."),
35
+ }, async (args) => textJson(await cloudRestore({ project: args.project, version: args.version, checkpoint: args.checkpointStamp, face: "mcp" })));
36
+ server.tool("summer_cloud_checkpoints", "List local pre-sync checkpoints.", projectArg.shape, async (args) => textJson(await cloudCheckpoints({ project: args.project, face: "mcp" })));
37
+ server.tool("summer_cloud_conflicts", "List local Summer Cloud conflict sets, or restore a preserved conflict file with restorePath.", {
38
+ ...projectArg.shape,
39
+ restorePath: z.string().optional().describe("Project-relative path to restore from the conflict sets."),
40
+ set: z.string().optional().describe("Conflict set stamp; defaults to the newest set containing the path."),
41
+ }, async (args) => textJson(await cloudConflicts({ project: args.project, restorePath: args.restorePath, set: args.set, face: "mcp" })));
42
+ }
@@ -39,22 +39,52 @@ Use after summer_get_diagnostics indicates console issues.`, {
39
39
  return result;
40
40
  }));
41
41
  server.tool("summer_clear_console", "Clear the editor's Output panel. Useful before running the game to get a clean slate for error checking.", {}, async () => withEngine(async (client) => client.executeOps([{ op: "ClearConsoleOutput" }])));
42
- server.tool("summer_get_debugger_errors", `Read runtime errors and warnings from the debugger. These are errors that occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.
42
+ server.tool("summer_get_debugger_errors", `Read runtime errors from the debugger. These occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.
43
+
44
+ For warning text, use summer_get_debugger_warnings (separate tool — engine returns warning count here but not the bodies).
43
45
 
44
46
  Output is deduped: identical errors firing every frame collapse into one entry with a "(×N)" count suffix. A "_filter" summary tells you exactly what was collapsed or truncated. Use raw=true to bypass shaping when you really need every entry.`, {
45
47
  max_errors: z.number().optional().default(50).describe("Max errors to return after dedupe"),
46
48
  include_stack: z.boolean().optional().describe("Include stack traces for each error"),
49
+ include_warnings: z.boolean().optional().default(false).describe("Forward-compat flag — when engine supports it, returns warning bodies in the same call. Today the engine ignores it; use summer_get_debugger_warnings instead."),
47
50
  raw: z.boolean().optional().default(false).describe("Bypass dedupe — return engine output verbatim"),
48
- }, async ({ max_errors, include_stack, raw }) => withEngine(async (client) => {
51
+ }, async ({ max_errors, include_stack, include_warnings, raw }) => withEngine(async (client) => {
49
52
  const op = { op: "GetDebuggerErrors", max_errors };
50
53
  if (include_stack !== undefined)
51
54
  op.include_stack = include_stack;
55
+ if (include_warnings)
56
+ op.include_warnings = true;
52
57
  const engineResult = await client.executeOps([op]);
53
58
  if (raw)
54
59
  return engineResult;
55
60
  const { result } = shapeEngineLogResponse(engineResult, { maxEntries: max_errors });
56
61
  return result;
57
62
  }));
63
+ server.tool("summer_get_debugger_warnings", `Read runtime warnings from the debugger panel. Warnings are non-fatal issues the game flags during play: missing optional resources, dead signal connections, deprecated API use, large allocations, physics warnings, etc.
64
+
65
+ Returns structured entries with file/line/function/error_descr/callstack — same shape as summer_get_debugger_errors but filtered to severity = "warning". Engine-internal warnings without a source file are filtered out as noise.
66
+
67
+ Use this when summer_get_diagnostics shows a non-zero \`debugger.warnings\` count and you want to see what they actually say.`, {
68
+ max_warnings: z.number().optional().default(50).describe("Max warnings to return after dedupe"),
69
+ include_stack: z.boolean().optional().default(true).describe("Include stack traces"),
70
+ raw: z.boolean().optional().default(false).describe("Bypass dedupe"),
71
+ }, async ({ max_warnings, include_stack, raw }) => withEngine(async (client) => {
72
+ // Reuses the existing GetDebuggerErrors op with type: "warning" filter.
73
+ // Engine-side change: ScriptEditorDebugger::get_errors_data() supports
74
+ // include_warnings; debug_ops::get_debugger_errors honours `type` to
75
+ // return warnings only. Diagnostics also exposes `warnings_data`.
76
+ const op = {
77
+ op: "GetDebuggerErrors",
78
+ max_errors: max_warnings,
79
+ type: "warning",
80
+ include_stack,
81
+ };
82
+ const engineResult = await client.executeOps([op]);
83
+ if (raw)
84
+ return engineResult;
85
+ const { result } = shapeEngineLogResponse(engineResult, { maxEntries: max_warnings });
86
+ return result;
87
+ }));
58
88
  server.tool("summer_play", `Start running the game in the engine. The game runs inside Summer Engine's viewport.
59
89
 
60
90
  After starting, use summer_get_diagnostics to check for runtime errors.
@@ -1,12 +1,88 @@
1
+ import { createRequire } from "node:module";
1
2
  import { z } from "zod";
2
3
  import { writeFile, mkdir } from "fs/promises";
3
4
  import { tmpdir } from "os";
4
5
  import { join } from "path";
5
6
  import { getAuthToken } from "../../lib/auth.js";
7
+ const require = createRequire(import.meta.url);
8
+ const { version: CLI_VERSION } = require("../../../package.json");
6
9
  const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
10
+ const TOOL_BY_ENDPOINT = {
11
+ "/api/mcp/generate/image": "summer_generate_image",
12
+ "/api/mcp/generate/audio": "summer_generate_audio",
13
+ "/api/mcp/generate/3d": "summer_generate_3d",
14
+ "/api/mcp/generate/video": "summer_generate_video",
15
+ "/api/mcp/generate/motion": "summer_generate_motion",
16
+ };
7
17
  // ---------------------------------------------------------------------------
8
18
  // Shared helpers
9
19
  // ---------------------------------------------------------------------------
20
+ function asRecord(value) {
21
+ return value && typeof value === "object" && !Array.isArray(value)
22
+ ? value
23
+ : null;
24
+ }
25
+ function stringFrom(value) {
26
+ return typeof value === "string" && value.length > 0 ? value : undefined;
27
+ }
28
+ function formatValidationDetailEntry(entry) {
29
+ const record = asRecord(entry);
30
+ if (!record) {
31
+ return stringFrom(entry) ?? null;
32
+ }
33
+ const message = stringFrom(record.msg) ??
34
+ stringFrom(record.message) ??
35
+ stringFrom(record.error) ??
36
+ null;
37
+ if (!message)
38
+ return null;
39
+ const loc = Array.isArray(record.loc)
40
+ ? record.loc.map((part) => String(part)).join(".")
41
+ : stringFrom(record.path) ?? stringFrom(record.field);
42
+ return loc ? `${loc}: ${message}` : message;
43
+ }
44
+ function formatValidationDetails(detail) {
45
+ if (Array.isArray(detail)) {
46
+ return detail
47
+ .map(formatValidationDetailEntry)
48
+ .filter((entry) => !!entry);
49
+ }
50
+ const formatted = formatValidationDetailEntry(detail);
51
+ return formatted ? [formatted] : [];
52
+ }
53
+ function formattedApiErrorMessage(status, data) {
54
+ const record = asRecord(data);
55
+ const provider = asRecord(record?.provider);
56
+ const directDetailMessages = formatValidationDetails(record?.detail);
57
+ const detailMessages = directDetailMessages.length > 0
58
+ ? directDetailMessages
59
+ : Array.isArray(provider?.detailMessages)
60
+ ? provider.detailMessages.filter((entry) => typeof entry === "string")
61
+ : formatValidationDetails(provider?.detail);
62
+ if (status === 422 && detailMessages.length > 0) {
63
+ return `Request validation failed (422): ${detailMessages.join("; ")}`;
64
+ }
65
+ return (stringFrom(record?.message) ??
66
+ stringFrom(record?.error) ??
67
+ `Request failed (${status})`);
68
+ }
69
+ async function readJsonResponse(res) {
70
+ if (typeof res.text !== "function") {
71
+ if (typeof res.json === "function") {
72
+ return res.json().catch(() => ({}));
73
+ }
74
+ return {};
75
+ }
76
+ const text = await res.text().catch(() => "");
77
+ if (!text)
78
+ return {};
79
+ try {
80
+ return JSON.parse(text);
81
+ }
82
+ catch {
83
+ return { message: text.slice(0, 1000) };
84
+ }
85
+ }
10
86
  async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
11
87
  const token = await getAuthToken();
12
88
  if (!token) {
@@ -15,18 +91,30 @@ async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
15
91
  status: 401,
16
92
  };
17
93
  }
18
- const res = await fetch(`${GATEWAY_URL}${endpoint}`, {
19
- method: "POST",
20
- headers: {
21
- Authorization: `Bearer ${token}`,
22
- "Content-Type": "application/json",
23
- },
24
- body: JSON.stringify(body),
25
- signal: AbortSignal.timeout(timeoutMs),
26
- });
27
- const data = await res.json();
94
+ let res;
95
+ try {
96
+ res = await fetch(`${GATEWAY_URL}${endpoint}`, {
97
+ method: "POST",
98
+ headers: {
99
+ Authorization: `Bearer ${token}`,
100
+ "Content-Type": "application/json",
101
+ "X-Summer-Client": "summer-cli",
102
+ "X-Summer-Client-Version": CLI_VERSION,
103
+ "X-Summer-Client-Surface": "mcp",
104
+ "X-Summer-MCP-Endpoint": endpoint,
105
+ "X-Summer-MCP-Tool": TOOL_BY_ENDPOINT[endpoint] ?? "unknown",
106
+ },
107
+ body: JSON.stringify(body),
108
+ signal: AbortSignal.timeout(timeoutMs),
109
+ });
110
+ }
111
+ catch (err) {
112
+ const msg = err instanceof Error ? err.message : String(err);
113
+ return { error: `Generation request failed: ${msg}`, status: 0 };
114
+ }
115
+ const data = await readJsonResponse(res);
28
116
  if (!res.ok) {
29
- let message = data.message || data.error || "Request failed";
117
+ let message = formattedApiErrorMessage(res.status, data);
30
118
  if (res.status === 402) {
31
119
  message =
32
120
  (data.message || "Insufficient credits.") +
@@ -37,7 +125,7 @@ async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
37
125
  (data.message || "Auth token expired.") +
38
126
  "\nRe-authenticate:\n npx summer-engine login";
39
127
  }
40
- return { error: message, data, status: res.status };
128
+ return { error: message, data: { ...data, status: res.status }, status: res.status };
41
129
  }
42
130
  return { data, status: res.status };
43
131
  }
@@ -102,7 +190,7 @@ function errorResult(message, extra) {
102
190
  content: [
103
191
  {
104
192
  type: "text",
105
- text: JSON.stringify({ error: true, message, ...extra }, null, 2),
193
+ text: JSON.stringify({ error: true, ...extra, message }, null, 2),
106
194
  },
107
195
  ],
108
196
  isError: true,