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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +24 -8
- package/.codex-plugin/plugin.json +19 -8
- package/.cursor-plugin/plugin.json +18 -7
- package/.factory-plugin/plugin.json +3 -3
- package/.opencode/INSTALL.md +1 -1
- package/AGENTS.md +6 -4
- package/CHANGELOG.md +46 -7
- package/CLAUDE.md +2 -2
- package/GEMINI.md +5 -6
- package/README.md +116 -109
- package/_persona/summer/SKILL.md +5 -6
- package/commands/gameskill.md +103 -0
- package/dist/bin/summer.js +17 -4
- package/dist/commands/cloud.d.ts +2 -0
- package/dist/commands/cloud.js +80 -0
- package/dist/commands/create.js +2 -0
- package/dist/commands/doctor.js +90 -1
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +63 -4
- package/dist/commands/login.js +4 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/memory.d.ts +14 -0
- package/dist/commands/memory.js +196 -0
- package/dist/commands/open.js +1 -1
- package/dist/commands/plan.d.ts +2 -0
- package/dist/commands/plan.js +60 -0
- package/dist/commands/run.js +132 -35
- package/dist/commands/setup.js +8 -2
- package/dist/commands/skills.js +90 -6
- package/dist/commands/status.js +3 -0
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +134 -7
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +63 -26
- package/dist/lib/async-op-lifecycle.d.ts +60 -0
- package/dist/lib/async-op-lifecycle.js +83 -0
- package/dist/lib/auth.d.ts +2 -0
- package/dist/lib/auth.js +13 -0
- package/dist/lib/cloud/api.d.ts +79 -0
- package/dist/lib/cloud/api.js +107 -0
- package/dist/lib/cloud/apply.d.ts +32 -0
- package/dist/lib/cloud/apply.js +294 -0
- package/dist/lib/cloud/checkpoint.d.ts +26 -0
- package/dist/lib/cloud/checkpoint.js +142 -0
- package/dist/lib/cloud/conflicts.d.ts +55 -0
- package/dist/lib/cloud/conflicts.js +131 -0
- package/dist/lib/cloud/containment.d.ts +8 -0
- package/dist/lib/cloud/containment.js +52 -0
- package/dist/lib/cloud/diff.d.ts +39 -0
- package/dist/lib/cloud/diff.js +138 -0
- package/dist/lib/cloud/engine-bridge.d.ts +21 -0
- package/dist/lib/cloud/engine-bridge.js +105 -0
- package/dist/lib/cloud/hash-cache.d.ts +23 -0
- package/dist/lib/cloud/hash-cache.js +44 -0
- package/dist/lib/cloud/hash.d.ts +10 -0
- package/dist/lib/cloud/hash.js +111 -0
- package/dist/lib/cloud/http.d.ts +17 -0
- package/dist/lib/cloud/http.js +47 -0
- package/dist/lib/cloud/lock-path.d.ts +1 -0
- package/dist/lib/cloud/lock-path.js +5 -0
- package/dist/lib/cloud/lock.d.ts +14 -0
- package/dist/lib/cloud/lock.js +143 -0
- package/dist/lib/cloud/manifest.d.ts +6 -0
- package/dist/lib/cloud/manifest.js +41 -0
- package/dist/lib/cloud/paths.d.ts +20 -0
- package/dist/lib/cloud/paths.js +90 -0
- package/dist/lib/cloud/project-godot-merge.d.ts +32 -0
- package/dist/lib/cloud/project-godot-merge.js +185 -0
- package/dist/lib/cloud/rules.d.ts +39 -0
- package/dist/lib/cloud/rules.js +159 -0
- package/dist/lib/cloud/sync.d.ts +21 -0
- package/dist/lib/cloud/sync.js +518 -0
- package/dist/lib/cloud/transfer.d.ts +23 -0
- package/dist/lib/cloud/transfer.js +184 -0
- package/dist/lib/cloud/types.d.ts +62 -0
- package/dist/lib/cloud/validate-path.d.ts +23 -0
- package/dist/lib/cloud/validate-path.js +103 -0
- package/dist/lib/engine.d.ts +6 -0
- package/dist/lib/engine.js +36 -1
- package/dist/lib/game-task-plan.d.ts +42 -0
- package/dist/lib/game-task-plan.js +376 -0
- package/dist/lib/project-memory.d.ts +33 -0
- package/dist/lib/project-memory.js +220 -0
- package/dist/lib/skills-registry.d.ts +134 -61
- package/dist/lib/skills-registry.js +102 -4
- package/dist/lib/version-check.d.ts +106 -0
- package/dist/lib/version-check.js +338 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +83 -0
- package/dist/mcp/tools/asset-tools.js +265 -45
- package/dist/mcp/tools/cloud-tools.d.ts +2 -0
- package/dist/mcp/tools/cloud-tools.js +42 -0
- package/dist/mcp/tools/debug-tools.js +32 -2
- package/dist/mcp/tools/generate-tools.js +101 -13
- package/dist/mcp/tools/project-tools.js +76 -21
- package/dist/mcp/tools/with-engine.d.ts +14 -0
- package/dist/mcp/tools/with-engine.js +52 -7
- package/gemini-extension.json +3 -3
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +20 -7
- package/skills/2d-assets/character-portrait/SKILL.md +1 -1
- package/skills/2d-assets/concept-art/SKILL.md +1 -1
- package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
- package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
- package/skills/2d-assets/pixel-art/SKILL.md +1 -1
- package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
- package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
- package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
- package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
- package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
- package/skills/3d-assets/character-model/SKILL.md +28 -6
- package/skills/3d-assets/environment-kit/SKILL.md +7 -2
- package/skills/3d-assets/organic-model/SKILL.md +5 -4
- package/skills/3d-assets/prop-model/SKILL.md +17 -9
- package/skills/3d-assets/vehicle-model/SKILL.md +5 -4
- package/skills/README.md +37 -30
- package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
- package/skills/animation/animation-tree/SKILL.md +4 -2
- package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
- package/skills/animation/generate-motion/SKILL.md +26 -5
- package/skills/animation/procedural-animation/SKILL.md +2 -2
- package/skills/asset-pipeline/asset-strategy/SKILL.md +3 -2
- package/skills/audio/adaptive-music/SKILL.md +1 -1
- package/skills/audio/audio-direction/SKILL.md +5 -5
- package/skills/audio/sound-effect/SKILL.md +2 -2
- package/skills/audio/voice-line/SKILL.md +25 -3
- package/skills/character-controllers/fps-controller/SKILL.md +5 -5
- package/skills/debugging/debug/SKILL.md +39 -1
- package/skills/deployment/export-and-ship/SKILL.md +4 -4
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
- package/skills/level-design/design-level/SKILL.md +5 -5
- package/skills/level-design/scene-to-level/SKILL.md +125 -0
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
- package/skills/performance/tune-performance/SKILL.md +5 -5
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
- package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
- package/skills/scene-and-project/play/SKILL.md +1 -1
- package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
- package/skills/video/animated-loop/SKILL.md +1 -1
- package/skills/video/cinematic-cutscene/SKILL.md +1 -1
- package/skills/video/trailer-shot/SKILL.md +1 -1
- package/skills/visual-effects/game-feel/SKILL.md +6 -6
- package/skills/workflow/brainstorming/SKILL.md +164 -0
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
- package/skills/workflow/brainstorming/scripts/helper.js +88 -0
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/workflow/brainstorming/visual-companion.md +287 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
- package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/workflow/gameskill/SKILL.md +111 -0
- package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
- package/skills/workflow/investigating-bugs/SKILL.md +296 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
- package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
- package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
- package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
- package/skills/workflow/investigating-bugs/test-academic.md +14 -0
- package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
- package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
- package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
- package/skills/workflow/mcpupdate/SKILL.md +134 -0
- package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
- package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
- package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
- package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
- package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
- package/skills/workflow/verification-before-completion/SKILL.md +139 -0
- package/skills/workflow/writing-plans/SKILL.md +152 -0
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/workflow/writing-skills/SKILL.md +655 -0
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
- package/skills/workflow/writing-skills/render-graphs.js +168 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
- package/dist/commands/assets.d.ts +0 -2
- package/dist/commands/assets.js +0 -189
- package/dist/commands/debug.d.ts +0 -2
- package/dist/commands/debug.js +0 -127
- package/dist/commands/doctor.test.js +0 -29
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.js +0 -170
- package/dist/commands/scene.d.ts +0 -2
- package/dist/commands/scene.js +0 -290
- package/dist/lib/agent-config.test.d.ts +0 -1
- package/dist/lib/agent-config.test.js +0 -80
- package/dist/lib/format.test.d.ts +0 -1
- package/dist/lib/format.test.js +0 -36
- package/dist/lib/plugin-manifests.test.d.ts +0 -1
- package/dist/lib/plugin-manifests.test.js +0 -134
- package/dist/lib/with-engine.d.ts +0 -14
- package/dist/lib/with-engine.js +0 -109
- package/skills/_shared/collaborative-protocol.md +0 -50
- package/skills/_shared/gd-style.md +0 -121
- package/skills/_shared/godot-version.md +0 -32
- package/skills/_shared/mcp-tools-reference.md +0 -133
- package/skills/_shared/summer-folder.md +0 -44
- package/skills/_shared/template-registry.md +0 -40
- package/skills/_tests/runner.md +0 -90
- package/skills/_tests/specs/3d-lighting.md +0 -19
- package/skills/_tests/specs/art-direction.md +0 -115
- package/skills/_tests/specs/asset-strategy.md +0 -19
- package/skills/_tests/specs/audio-direction.md +0 -123
- package/skills/_tests/specs/brainstorm-game.md +0 -99
- package/skills/_tests/specs/debug.md +0 -94
- package/skills/_tests/specs/design-level.md +0 -113
- package/skills/_tests/specs/design-mechanic.md +0 -112
- package/skills/_tests/specs/design-npc.md +0 -143
- package/skills/_tests/specs/export-and-ship.md +0 -114
- package/skills/_tests/specs/fps-controller.md +0 -113
- package/skills/_tests/specs/gdscript-patterns.md +0 -19
- package/skills/_tests/specs/host-authoritative-state.md +0 -104
- package/skills/_tests/specs/make-game.md +0 -19
- package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
- package/skills/_tests/specs/play.md +0 -88
- package/skills/_tests/specs/scene-composition.md +0 -19
- package/skills/_tests/specs/setup-multiplayer.md +0 -106
- package/skills/_tests/specs/tune-performance.md +0 -104
- package/skills/_tests/specs/ui-basics.md +0 -19
- package/skills/_tests/specs/vfx.md +0 -116
- package/skills/catalog.yaml +0 -991
- /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
|
-
|
|
142
|
-
|
|
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
|
|
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:
|
|
228
|
-
addedToScene:
|
|
229
|
-
parent: parent
|
|
230
|
-
message:
|
|
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 ${
|
|
452
|
+
: `Imported "${best.title}" to ${imported.importedTo}`,
|
|
233
453
|
}, null, 2),
|
|
234
454
|
},
|
|
235
455
|
],
|
|
@@ -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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 =
|
|
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,
|
|
193
|
+
text: JSON.stringify({ error: true, ...extra, message }, null, 2),
|
|
106
194
|
},
|
|
107
195
|
],
|
|
108
196
|
isError: true,
|