summer-engine 3.2.1 → 3.4.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 (97) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/CHANGELOG.md +61 -0
  7. package/README.md +7 -4
  8. package/assets/api-docs.json.gz +0 -0
  9. package/dist/cli/commands/install.d.ts +10 -0
  10. package/dist/cli/commands/install.js +55 -10
  11. package/dist/cli/commands/login.js +12 -0
  12. package/dist/cli/commands/publish.js +2 -1
  13. package/dist/cli/commands/tool.d.ts +7 -0
  14. package/dist/cli/commands/tool.js +36 -1
  15. package/dist/core/auth.js +3 -0
  16. package/dist/core/capabilities/audit/args.d.ts +14 -14
  17. package/dist/core/capabilities/capture-gameplay.d.ts +69 -0
  18. package/dist/core/capabilities/capture-gameplay.js +283 -0
  19. package/dist/core/capabilities/creator.js +1 -1
  20. package/dist/core/capabilities/doctor.d.ts +9 -1
  21. package/dist/core/capabilities/doctor.js +47 -4
  22. package/dist/core/capabilities/engine-run.d.ts +12 -0
  23. package/dist/core/capabilities/engine-run.js +61 -0
  24. package/dist/core/capabilities/export-engine-version.d.ts +15 -0
  25. package/dist/core/capabilities/export-engine-version.js +24 -0
  26. package/dist/core/capabilities/export-game.d.ts +107 -0
  27. package/dist/core/capabilities/export-game.js +445 -0
  28. package/dist/core/capabilities/export-presets.d.ts +50 -0
  29. package/dist/core/capabilities/export-presets.js +177 -0
  30. package/dist/core/capabilities/export-templates.d.ts +118 -0
  31. package/dist/core/capabilities/export-templates.js +291 -0
  32. package/dist/core/capabilities/image-generation.d.ts +9 -0
  33. package/dist/core/capabilities/image-generation.js +19 -1
  34. package/dist/core/capabilities/publish-build.d.ts +77 -0
  35. package/dist/core/capabilities/publish-build.js +500 -0
  36. package/dist/core/capabilities/publish-download.d.ts +37 -0
  37. package/dist/core/capabilities/publish-download.js +168 -0
  38. package/dist/core/capabilities/runtime-control.d.ts +10 -10
  39. package/dist/core/capabilities/summer-bundle.d.ts +49 -0
  40. package/dist/core/capabilities/summer-bundle.js +156 -0
  41. package/dist/core/capabilities/tool-dispatch.js +43 -1
  42. package/dist/core/capabilities/ui-control.d.ts +2 -2
  43. package/dist/core/config.js +2 -2
  44. package/dist/core/engine-install.d.ts +16 -0
  45. package/dist/core/engine-install.js +32 -2
  46. package/dist/core/hosted-mcp.d.ts +115 -0
  47. package/dist/core/hosted-mcp.js +164 -0
  48. package/dist/core/launch-posture.d.ts +3 -0
  49. package/dist/core/launch-posture.js +15 -2
  50. package/dist/core/library-read.d.ts +2 -2
  51. package/dist/core/oauth.d.ts +78 -0
  52. package/dist/core/oauth.js +309 -0
  53. package/dist/core/util/zip-write.d.ts +18 -0
  54. package/dist/core/util/zip-write.js +112 -0
  55. package/dist/core/util/zip.d.ts +22 -0
  56. package/dist/core/util/zip.js +140 -0
  57. package/dist/mcp/hosted-mount.d.ts +50 -0
  58. package/dist/mcp/hosted-mount.js +200 -0
  59. package/dist/mcp/server.d.ts +2 -1
  60. package/dist/mcp/server.js +14 -6
  61. package/dist/mcp/tools/creator-tools.js +64 -1
  62. package/dist/mcp/tools/generate-tools.js +12 -2
  63. package/gemini-extension.json +3 -1
  64. package/library/references/mcp-tools-reference/mcp-tools-reference.md +7 -3
  65. package/library/skills/README.md +6 -4
  66. package/library/skills/bounded-engine-runs/SKILL.md +96 -0
  67. package/library/skills/bounded-engine-runs/resource.yaml +29 -0
  68. package/library/skills/compatibility-renderer-traps/SKILL.md +78 -0
  69. package/library/skills/compatibility-renderer-traps/resource.yaml +28 -0
  70. package/library/skills/concept-art/SKILL.md +2 -2
  71. package/library/skills/export-and-ship/SKILL.md +4 -0
  72. package/library/skills/headless-scripting/resource.yaml +1 -0
  73. package/library/skills/in-game-purchases/SKILL.md +123 -118
  74. package/library/skills/in-game-purchases/resource.yaml +2 -2
  75. package/library/skills/multiplayer-publish/SKILL.md +5 -1
  76. package/library/skills/navigate-summer/SKILL.md +1 -1
  77. package/library/skills/web-compatibility-look/resource.yaml +1 -0
  78. package/library/tools/capture-gameplay/resource.yaml +73 -0
  79. package/library/tools/creator-publish/resource.yaml +4 -3
  80. package/library/tools/creator-releases/resource.yaml +1 -1
  81. package/library/tools/export-game/resource.yaml +72 -0
  82. package/library/tools/export-templates/resource.yaml +63 -0
  83. package/library/tools/generate-image/resource.yaml +15 -1
  84. package/library/tools/open/resource.yaml +1 -1
  85. package/library/tools/publish-build/resource.yaml +78 -0
  86. package/package.json +1 -1
  87. package/registry/generated/aliases.json +4 -0
  88. package/registry/generated/counts.json +3 -3
  89. package/registry/generated/gemini-extension.json +3 -1
  90. package/registry/generated/index.json +241 -19
  91. package/registry/generated/marketplace.claude.json +2 -2
  92. package/registry/generated/plugin.claude.json +4 -2
  93. package/registry/generated/plugin.codex.json +5 -3
  94. package/registry/generated/plugin.cursor.json +4 -2
  95. package/registry/generated/plugin.factory.json +4 -2
  96. package/registry/generated/skills-index.md +6 -4
  97. package/registry/generated/skills-registry.json +28 -1
@@ -0,0 +1,200 @@
1
+ import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
+ import { z } from "zod";
3
+ import { connectHostedMcp } from "../core/hosted-mcp.js";
4
+ import { getStoreAccessToken, OAuthError, resolveMcpResourceUrl } from "../core/oauth.js";
5
+ import { appendMcpLogEvent } from "../core/mcp-log.js";
6
+ /**
7
+ * One Summer Engine MCP. The cloud tools a creator's agent needs to publish
8
+ * (projects, store page, build upload, submit for the owner's approval, the
9
+ * board, Grow) live on the hosted Summer Engine MCP at mcp.summerengine.com,
10
+ * the same server ChatGPT and Claude connect to. This module mounts them into
11
+ * the local MCP so a local agent sees one server: each hosted tool, prompt
12
+ * and resource is registered here under its own name and forwarded with the
13
+ * person's store sign-in ("summer login --store"; the token's audience is
14
+ * that server). A name the local MCP already has stays local (the engine and
15
+ * generation tools keep their CLI-token path).
16
+ *
17
+ * Mounting runs after connect. The first tools/list waits for a mount in
18
+ * progress (up to FIRST_LIST_WAIT_MS), so a host that lists tools once still
19
+ * sees the store tools; a slower mount adds them later (tools/list_changed).
20
+ * Without a store sign-in the mount fails at once, and one tool,
21
+ * summer_store_tools, says how to sign in and mounts on the next call.
22
+ */
23
+ export const STORE_TOOLS_STATUS = "summer_store_tools";
24
+ /** How long the first tools/list waits for a mount in progress, so hosts that list once see the store tools. */
25
+ export const FIRST_LIST_WAIT_MS = 5_000;
26
+ const passthroughArgs = z.object({}).passthrough();
27
+ export const defaultHostedMountDependencies = {
28
+ url: () => resolveMcpResourceUrl(),
29
+ token: () => getStoreAccessToken(),
30
+ connect: connectHostedMcp,
31
+ };
32
+ async function listAll(page, key) {
33
+ const out = [];
34
+ let cursor;
35
+ for (let pages = 0; pages < 20; pages++) {
36
+ const result = await page(cursor);
37
+ out.push(...(result[key] ?? []));
38
+ if (!result.nextCursor)
39
+ break;
40
+ cursor = result.nextCursor;
41
+ }
42
+ return out;
43
+ }
44
+ function errorText(error) {
45
+ return { content: [{ type: "text", text: JSON.stringify({ ok: false, ...error }) }], isError: true };
46
+ }
47
+ /**
48
+ * Get the server ready for hosted tools before the transport connects (the
49
+ * SDK fixes capabilities at connect): resource handlers exist, and tools/list
50
+ * reports each mounted tool's own JSON Schema rather than the passthrough one.
51
+ * Returns mount(), which callers run after connect without awaiting it.
52
+ */
53
+ export function prepareHostedMount(server, deps = defaultHostedMountDependencies, options = {}) {
54
+ const firstListWaitMs = options.firstListWaitMs ?? FIRST_LIST_WAIT_MS;
55
+ let listed = false;
56
+ const state = { status: "idle", tools: [], prompts: [], resources: [], collisions: [] };
57
+ const schemas = new Map();
58
+ const registries = server;
59
+ // Declare the resources capability now: registering one and removing it
60
+ // keeps the handlers (and capability) without listing anything yet.
61
+ server.registerResource("summer-hosted-placeholder", "summer://hosted/placeholder", {}, async () => ({ contents: [] })).remove();
62
+ // tools/list as the SDK builds it, with mounted tools carrying their hosted schema.
63
+ const handlers = server.server._requestHandlers;
64
+ const listTools = handlers.get("tools/list");
65
+ if (listTools) {
66
+ server.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
67
+ if (!listed) {
68
+ listed = true;
69
+ await waitForMount(firstListWaitMs);
70
+ }
71
+ const result = await listTools(request, extra);
72
+ return {
73
+ ...result,
74
+ tools: result.tools.map((tool) => {
75
+ const hosted = schemas.get(tool.name);
76
+ return hosted ? { ...tool, inputSchema: hosted.inputSchema, ...(hosted.annotations ? { annotations: hosted.annotations } : {}), ...(hosted.title ? { title: hosted.title } : {}) } : tool;
77
+ }),
78
+ };
79
+ });
80
+ }
81
+ let statusTool = null;
82
+ let running = null;
83
+ /** Wait for a mount in progress, at most ms; returns at once when none runs. */
84
+ async function waitForMount(ms) {
85
+ if (!running || ms <= 0)
86
+ return;
87
+ let timer;
88
+ await Promise.race([running, new Promise((done) => (timer = setTimeout(done, ms)))]);
89
+ clearTimeout(timer);
90
+ }
91
+ const showStatusTool = () => {
92
+ if (statusTool || STORE_TOOLS_STATUS in registries._registeredTools)
93
+ return;
94
+ statusTool = server.registerTool(STORE_TOOLS_STATUS, {
95
+ description: "The Summer store, publishing, board and Grow tools come from the hosted Summer Engine MCP and are not loaded. Call this to see why and how to fix it (usually: run \"summer login --store\" in a terminal); it loads them when the sign-in works.",
96
+ inputSchema: passthroughArgs,
97
+ }, async () => {
98
+ const after = await mount();
99
+ if (after.status === "mounted") {
100
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, loaded: after.tools.length, tools: after.tools }) }] };
101
+ }
102
+ return errorText(after.error ?? { code: "store_tools_unavailable", message: "The hosted tools are not loaded.", recovery: 'Recovery: run "summer login --store", then call this again.' });
103
+ });
104
+ };
105
+ async function attempt() {
106
+ let client;
107
+ try {
108
+ const url = deps.url();
109
+ await deps.token();
110
+ client = await deps.connect(url, deps.token);
111
+ }
112
+ catch (error) {
113
+ state.status = "unavailable";
114
+ state.error =
115
+ error instanceof OAuthError
116
+ ? { code: error.code, message: error.message.replace(` ${error.recovery}`, ""), recovery: error.recovery }
117
+ : {
118
+ code: "store_tools_unreachable",
119
+ message: `The hosted Summer Engine MCP did not answer: ${error instanceof Error ? error.message : String(error)}.`,
120
+ recovery: 'Recovery: check the network; if it says unauthorized, run "summer login --store --force". Then call summer_store_tools.',
121
+ };
122
+ appendMcpLogEvent("mcp:hosted_mount_failed", { code: state.error.code });
123
+ showStatusTool();
124
+ return state;
125
+ }
126
+ const [tools, prompts, resources] = await Promise.all([
127
+ listAll((cursor) => client.listTools(cursor ? { cursor } : undefined), "tools"),
128
+ listAll((cursor) => client.listPrompts(cursor ? { cursor } : undefined), "prompts").catch(() => []),
129
+ listAll((cursor) => client.listResources(cursor ? { cursor } : undefined), "resources").catch(() => []),
130
+ ]);
131
+ for (const tool of tools) {
132
+ if (tool.name in registries._registeredTools && !schemas.has(tool.name)) {
133
+ if (tool.name !== STORE_TOOLS_STATUS)
134
+ state.collisions.push(tool.name);
135
+ continue;
136
+ }
137
+ if (schemas.has(tool.name))
138
+ continue;
139
+ schemas.set(tool.name, tool);
140
+ server.registerTool(tool.name, { description: tool.description ?? "", inputSchema: passthroughArgs, ...(tool.annotations ? { annotations: tool.annotations } : {}) }, async (args) => {
141
+ try {
142
+ return (await client.callTool({ name: tool.name, arguments: args }));
143
+ }
144
+ catch (error) {
145
+ return errorText({
146
+ code: "store_tool_failed",
147
+ message: `The hosted Summer Engine MCP did not complete ${tool.name}: ${error instanceof Error ? error.message : String(error)}.`,
148
+ recovery: 'Recovery: retry; if it says unauthorized, run "summer login --store --force".',
149
+ });
150
+ }
151
+ });
152
+ state.tools.push(tool.name);
153
+ }
154
+ for (const prompt of prompts) {
155
+ if (prompt.name in registries._registeredPrompts) {
156
+ if (!state.prompts.includes(prompt.name))
157
+ state.collisions.push(`prompt:${prompt.name}`);
158
+ continue;
159
+ }
160
+ const shape = Object.fromEntries((prompt.arguments ?? []).map((arg) => [arg.name, arg.required ? z.string().describe(arg.description ?? "") : z.string().optional().describe(arg.description ?? "")]));
161
+ server.registerPrompt(prompt.name, { ...(prompt.title ? { title: prompt.title } : {}), ...(prompt.description ? { description: prompt.description } : {}), ...(prompt.arguments?.length ? { argsSchema: shape } : {}) }, (async (args = {}) => client.getPrompt({ name: prompt.name, arguments: Object.fromEntries(Object.entries(args).filter((entry) => typeof entry[1] === "string")) })));
162
+ state.prompts.push(prompt.name);
163
+ }
164
+ for (const resource of resources) {
165
+ if (resource.uri in registries._registeredResources) {
166
+ if (!state.resources.includes(resource.uri))
167
+ state.collisions.push(`resource:${resource.uri}`);
168
+ continue;
169
+ }
170
+ server.registerResource(resource.name, resource.uri, { ...(resource.title ? { title: resource.title } : {}), ...(resource.description ? { description: resource.description } : {}), ...(resource.mimeType ? { mimeType: resource.mimeType } : {}) }, async (uri) => (await client.readResource({ uri: uri.href })));
171
+ state.resources.push(resource.uri);
172
+ }
173
+ statusTool?.remove();
174
+ statusTool = null;
175
+ state.status = "mounted";
176
+ delete state.error;
177
+ appendMcpLogEvent("mcp:hosted_mounted", { tools: state.tools.length, prompts: state.prompts.length, resources: state.resources.length, collisions: state.collisions.length });
178
+ return state;
179
+ }
180
+ /** Mount once; a failed mount runs again on the next call. */
181
+ function mount() {
182
+ if (state.status === "mounted")
183
+ return Promise.resolve(state);
184
+ if (!running) {
185
+ state.status = "mounting";
186
+ running = attempt()
187
+ .catch((error) => {
188
+ state.status = "unavailable";
189
+ state.error = { code: "store_tools_unreachable", message: error instanceof Error ? error.message : String(error), recovery: "Recovery: call summer_store_tools to retry." };
190
+ showStatusTool();
191
+ return state;
192
+ })
193
+ .finally(() => {
194
+ running = null;
195
+ });
196
+ }
197
+ return running;
198
+ }
199
+ return { mount, state };
200
+ }
@@ -10,7 +10,8 @@ export declare const getCachedBootDriftNotice: typeof getCachedNotice;
10
10
  * (docs/design/archive/E2E-2026-09-03.md, F-17) showed an agent needs on the first
11
11
  * turn and would otherwise learn the hard way. Kept under 600 characters so
12
12
  * it costs a few hundred tokens once; the full operating guide stays in the
13
- * summer_agent_playbook prompt / summer_get_agent_playbook tool.
13
+ * summer_agent_playbook prompt / summer_get_agent_playbook tool. The last
14
+ * sentence names the publishing path and the owner-approval rule.
14
15
  */
15
16
  export declare const SUMMER_MCP_INSTRUCTIONS: string;
16
17
  export declare function configureMcpEngineSelection(selection?: EngineSelection): void;
@@ -26,6 +26,7 @@ import { registerNavigationTools } from "./tools/navigation-tools.js";
26
26
  import { registerLibraryTools } from "./tools/library-tools.js";
27
27
  import { getCachedBootDriftNotice as getCachedNotice, setCachedBootDriftNotice, } from "./boot-notice.js";
28
28
  import { appendMcpLogEvent } from "../core/mcp-log.js";
29
+ import { prepareHostedMount } from "./hosted-mount.js";
29
30
  import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../installer/version-check.js";
30
31
  import { TOOLKIT_VERSION as version } from "../core/version.js";
31
32
  let processDiagnosticsInstalled = false;
@@ -37,13 +38,15 @@ export const getCachedBootDriftNotice = getCachedNotice;
37
38
  * (docs/design/archive/E2E-2026-09-03.md, F-17) showed an agent needs on the first
38
39
  * turn and would otherwise learn the hard way. Kept under 600 characters so
39
40
  * it costs a few hundred tokens once; the full operating guide stays in the
40
- * summer_agent_playbook prompt / summer_get_agent_playbook tool.
41
+ * summer_agent_playbook prompt / summer_get_agent_playbook tool. The last
42
+ * sentence names the publishing path and the owner-approval rule.
41
43
  */
42
- export const SUMMER_MCP_INSTRUCTIONS = "Summer Engine MCP. Call summer_get_project_context first: it binds this session to the open project and returns mainScene, projectMemory (GameSoul, template pin) and any capabilitySkewWarning, compactly. " +
43
- "Before building or fixing, summer_search_library then summer_read_library the hit: skills, examples, templates live there. " +
44
- "After a playthrough read summer_get_diagnostics, not summer_get_console alone (runtime errors live in the debugger). " +
45
- "An all-black screenshot means the viewport had not redrawn — recapture before concluding. " +
46
- "Report entries you verified in-engine via summer_library_feedback.";
44
+ export const SUMMER_MCP_INSTRUCTIONS = "Summer Engine MCP. Call summer_get_project_context first: it binds the open project and returns mainScene, projectMemory and any capabilitySkewWarning. " +
45
+ "Before building or fixing, summer_search_library then summer_read_library the hit. " +
46
+ "After a playthrough read summer_get_diagnostics, not summer_get_console alone. " +
47
+ "An all-black screenshot means the viewport had not redrawn: recapture. " +
48
+ "Report verified entries via summer_library_feedback. " +
49
+ "Publishing: summer_export_game, summer_publish_build, summer_store_*; an agent never publishes alone: summer_store_submit returns the owner's approval link.";
47
50
  /**
48
51
  * Fire-and-forget probe of the npm registry on MCP boot. Caches the result for
49
52
  * the session. We never await this in startup so a slow / offline registry
@@ -352,8 +355,13 @@ export async function startMcpServer(options = {}) {
352
355
  setCachedBootDriftNotice(null);
353
356
  appendMcpLogEvent("mcp:boot_drift_probe_failed", errorDetails(error));
354
357
  });
358
+ // Hosted Summer Engine MCP tools (store, publishing, board, Grow): ready
359
+ // before connect, mounted after it. The first tools/list waits a few
360
+ // seconds for the mount (not under summer doctor, which times the list).
361
+ const hosted = prepareHostedMount(server, undefined, process.env.SUMMER_MCP_DOCTOR === "1" ? { firstListWaitMs: 0 } : {});
355
362
  const transport = new StdioServerTransport();
356
363
  await server.connect(transport);
364
+ void hosted.mount();
357
365
  appendMcpLogEvent("mcp:ready", {
358
366
  version,
359
367
  pid: process.pid,
@@ -1,5 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { CreatorOperationError, listCreatorReleases, publishCreator, } from "../../core/capabilities/creator.js";
3
+ import { captureGameplay } from "../../core/capabilities/capture-gameplay.js";
4
+ import { exportGame } from "../../core/capabilities/export-game.js";
5
+ import { STORE_TARGETS } from "../../core/capabilities/export-presets.js";
6
+ import { exportTemplates, TEMPLATE_PLATFORMS } from "../../core/capabilities/export-templates.js";
7
+ import { publishBuild } from "../../core/capabilities/publish-build.js";
8
+ import { BuildToolError } from "../../core/capabilities/summer-bundle.js";
3
9
  import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, setConfigValue, unsetConfigValue, } from "../../core/config.js";
4
10
  import { textJson } from "./text-json.js";
5
11
  async function creatorResult(operation) {
@@ -16,6 +22,16 @@ async function creatorResult(operation) {
16
22
  recovery: error.recovery,
17
23
  }, true);
18
24
  }
25
+ if (error instanceof BuildToolError) {
26
+ return textJson({
27
+ ok: false,
28
+ code: error.code,
29
+ message: error.message.replace(` ${error.recovery}`, ""),
30
+ recovery: error.recovery,
31
+ ...(error.status ? { status: error.status } : {}),
32
+ ...error.detail,
33
+ }, true);
34
+ }
19
35
  return textJson({
20
36
  ok: false,
21
37
  code: "creator_request_invalid",
@@ -24,7 +40,54 @@ async function creatorResult(operation) {
24
40
  }
25
41
  }
26
42
  export function registerCreatorTools(server) {
27
- server.tool("summer_creator_publish", "Publish an exact exported Summer .pck through the versioned Summer Platform creator API. First call with confirm=false and present the returned project, version, digest, size, artifact path, channel, and notes; set confirm only after the user approves that exact target. The server independently verifies token scope, ownership, bytes, and review state.", {
43
+ server.tool("summer_export_game", "Export the game in the exact file the Summer Games store takes, with the installed Summer Engine headless (no window). format \"bundle\" (default): the summer.games .zip the Summer apps run on iPhone, Android, macOS and Windows; targets picks the platforms (a game without a server: ios and android only); upload it with summer_publish_build. format \"download\" with one target: web (an HTML5 .zip on Summer's WebGPU Forward+ web template, played on summer.games), macos (.app zip, macos-universal) or windows (.exe zip, windows-x64); these need the export template from summer_export_templates and are uploaded as store versions. Needs Summer Engine 0.7.0 or newer: an older engine is refused before anything is written (code engine_too_old). Works without the editor running. Returns path, sha256, size, the manifest or store platform, and warnings.", {
44
+ project: z.string().optional().describe("Project folder with project.godot. Defaults to the MCP's bound project, then the working directory."),
45
+ out: z.string().optional().describe("Output .zip path. Defaults to <project>/.summer/exports/."),
46
+ targets: z
47
+ .array(z.enum(STORE_TARGETS))
48
+ .optional()
49
+ .describe("Store platforms. Bundle: any of macos, windows, ios, android (omit to use every platform the project declares). Download: exactly one of web, macos, windows."),
50
+ format: z
51
+ .enum(["bundle", "download"])
52
+ .optional()
53
+ .describe("bundle (default): the summer.games store build. download: a web build or native download; the default when targets is [\"web\"]."),
54
+ debug: z.boolean().default(false).describe("Export a debug build (--export-debug)."),
55
+ timeoutSeconds: z.number().int().min(10).max(7200).optional().describe("Stop the engine after this many seconds (default 900; a first export imports every asset)."),
56
+ }, async ({ project, out, targets, format, debug, timeoutSeconds }) => creatorResult(() => exportGame({ project, out, targets, format, debug, ...(timeoutSeconds ? { timeoutMs: timeoutSeconds * 1000 } : {}) })));
57
+ server.tool("summer_export_templates", "List, download and install Summer Engine export templates from Summer's CDN into the folder the installed engine reads, checking each file's sha256. Needed only for summer_export_game format \"download\": web (Summer's WebGPU Forward+ template), macos, windows. Store bundles (format \"bundle\") need no template. action list shows what is installed and published; action install fetches the platforms asked (default web).", {
58
+ action: z.enum(["list", "install"]).describe("list: installed and published templates. install: download and install."),
59
+ platforms: z
60
+ .array(z.enum(TEMPLATE_PLATFORMS))
61
+ .optional()
62
+ .describe("Template platforms to install (default [\"web\"])."),
63
+ includeDebug: z.boolean().default(false).describe("Also install the debug templates (for exports with debug true)."),
64
+ summerVersion: z
65
+ .string()
66
+ .optional()
67
+ .describe("The installed Summer Engine version (Help > About, e.g. 0.7.1). Read automatically on macOS; needed on Windows and Linux."),
68
+ }, async ({ action, platforms, includeDebug, summerVersion }) => creatorResult(() => exportTemplates({ action, platforms, includeDebug, summerVersion })));
69
+ server.tool("summer_capture_gameplay", "Capture real gameplay frames without a running editor: starts the game in Summer Engine's offscreen verify instance (a real renderer, a window parked offscreen with no focus, never on screen), lets it run waitSeconds, and saves PNG frames of what the game draws, HUD included. Use for store screenshots (1920x1080 landscape or 1080x1920 portrait). With an editor open, summer_screenshot target game also works. Returns each frame's path, width and height; look at them before using them.", {
70
+ project: z.string().optional().describe("Project folder with project.godot. Defaults to the MCP's bound project, then the working directory."),
71
+ scene: z.string().optional().describe("Scene to start (res://... or uid://...). Default: the project's main scene, which is often a title menu; pass the gameplay scene for gameplay frames."),
72
+ resolution: z.string().optional().describe("Window size WIDTHxHEIGHT (default 1920x1080; 1080x1920 for portrait)."),
73
+ frames: z.number().int().min(1).max(10).optional().describe("Frames to save (default 1)."),
74
+ waitSeconds: z.number().min(0).max(120).optional().describe("Seconds the game runs before the first frame (default 3)."),
75
+ intervalSeconds: z.number().min(0.1).max(60).optional().describe("Seconds between frames (default 1)."),
76
+ out: z.string().optional().describe("Output folder. Defaults to <project>/.summer/captures/<time>/ (ignored by git)."),
77
+ }, async (args) => creatorResult(() => captureGameplay(args)));
78
+ server.tool("summer_publish_build", "Upload an export from summer_export_game to the creator's game on summer.games, through the same store upload Studio uses, straight from disk. A store bundle (format bundle): declare, upload parts, seal, wait until Summer makes the Build, name its client pack, and with publish=true approve it. A web build or native download (format download): upload it as the game's store version for its platform (web, macos-universal, windows-x64, linux-x64; read from the last export) and wait until Summer has checked it. First call with confirm=false: it checks that the game exists for this account and warns when its store page does not list the export's platforms, then returns the target (game name and id, file, digest, size, version) to show the user; set confirm only after they approve. Needs \"summer login --store\". A retry with the same file and clientVersion continues the same upload. Nothing here makes a game live: the owner approves publishing (summer_store_submit).", {
79
+ gameId: z.string().optional().describe("The store game id (from summer_store_list_games, or the Studio store page URL). Without it the call fails with game_required and lists your store games, or says why the store did not list them."),
80
+ file: z.string().optional().describe("Exported .zip. Defaults to the last summer_export_game result."),
81
+ clientVersion: z.string().describe("Build version, vMAJOR.MINOR.PATCH (e.g. v1.0.0); a new upload needs a new version."),
82
+ publish: z.boolean().default(false).describe("Bundles only: also approve the Build for players. A game that has not passed review keeps it as a preview; an agent needs the owner's approval."),
83
+ platform: z
84
+ .enum(["web", "macos-universal", "windows-x64", "linux-x64"])
85
+ .optional()
86
+ .describe("Web builds and native downloads: the store platform. Defaults to the last export's platform; omit for a store bundle."),
87
+ confirm: z.boolean().default(false).describe("Set true only after the user approves the exact upload."),
88
+ waitSeconds: z.number().int().min(0).max(1800).optional().describe("How long to wait for Summer to make the Build (default 600). On timeout, call again to keep waiting."),
89
+ }, async (args) => creatorResult(() => publishBuild({ ...args, face: "mcp" })));
90
+ server.tool("summer_creator_publish", "Deprecated: use summer_export_game, then summer_publish_build, which upload the summer.games .zip to the game's store listing. This old path publishes a .pck to the legacy Summercraft creator API with a separate sc_ token and will be removed. First call with confirm=false and present the returned project, version, digest, size, artifact path, channel, and notes; set confirm only after the user approves that exact target. The server independently verifies token scope, ownership, bytes, and review state.", {
28
91
  project: z.string().optional().describe("Project root. Defaults to the current working directory."),
29
92
  artifact: z.string().describe("Exact path to the exported Summer .pck artifact."),
30
93
  version: z.string().describe("Immutable release version."),
@@ -297,19 +297,29 @@ Style presets: "realistic" (default), "cartoon", "anime", "none"
297
297
  For an alpha PNG, set removeBackground: true. Prompt wording alone does not
298
298
  guarantee transparency; background removal runs server-side after generation.
299
299
 
300
- The 'options' object is passed directly to the AI provider for full control.
300
+ Size: aspectRatio (1:1, 16:9, 9:16, 4:3, 3:4, ...) or width + height (the
301
+ smallest size you need). For store art ask width 1920 height 1080 (key art)
302
+ or width 1080 height 1920 (tall cover): the server picks a model that reaches
303
+ that size, or refuses a named model that cannot, before anything is spent.
304
+ Each result reports the real width and height and the model that ran.
305
+
306
+ 'options' carries extra model params (seed, negative_prompt, ...); the result
307
+ lists any the server did not use in ignoredOptions.
301
308
 
302
309
  Returns the asset with fileUrl (hosted) and localPath (temp file on disk).
303
310
  Use the Read tool on localPath to show the image to the user for approval.
304
311
 
305
312
  Cloud tool — runs on Summer's servers and works WITHOUT the Summer Engine app open.
306
- Requires authentication: run 'npx -y summer-engine@latest login' first.`, imageGenerationArgsSchema.shape, async ({ prompt, model, style, referenceImageUrl, removeBackground, options }) => {
313
+ Requires authentication: run 'npx -y summer-engine@latest login' first.`, imageGenerationArgsSchema.shape, async ({ prompt, model, style, referenceImageUrl, removeBackground, aspectRatio, width, height, options }) => {
307
314
  const result = await mcpGenerate("/api/mcp/generate/image", {
308
315
  prompt,
309
316
  model,
310
317
  style,
311
318
  referenceImageUrl,
312
319
  removeBackground,
320
+ aspectRatio,
321
+ width,
322
+ height,
313
323
  options,
314
324
  });
315
325
  if (result.error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/gemini — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "version": "3.2.1",
4
+ "version": "3.4.0",
5
5
  "description": "Agent tooling for Summer Engine: MCP bridge, context primer, and game-dev skills. Use with the npm CLI for skill files on disk.",
6
6
  "contextFileName": "GEMINI.md",
7
7
  "mcpServers": {
@@ -35,6 +35,7 @@
35
35
  "./library/skills/asset-strategy/",
36
36
  "./library/skills/audio-direction/",
37
37
  "./library/skills/auto-fire-targeting/",
38
+ "./library/skills/bounded-engine-runs/",
38
39
  "./library/skills/brainstorm-game/",
39
40
  "./library/skills/brainstorming/",
40
41
  "./library/skills/browse-templates/",
@@ -46,6 +47,7 @@
46
47
  "./library/skills/character-portrait/",
47
48
  "./library/skills/cinematic-cutscene/",
48
49
  "./library/skills/combat-basics/",
50
+ "./library/skills/compatibility-renderer-traps/",
49
51
  "./library/skills/concept-art/",
50
52
  "./library/skills/create-asset-sheet/",
51
53
  "./library/skills/debug/",
@@ -17,7 +17,7 @@
17
17
 
18
18
  **Rule of thumb:** project reads/writes go through Summer; live hierarchy/inspector changes use scene tools; process-level work remains with the host.
19
19
 
20
- ## Tool surface (101 tools)
20
+ ## Tool surface (105 tools)
21
21
 
22
22
  ### Project files (4)
23
23
 
@@ -237,13 +237,17 @@ Preview. One fast, read-only call that walks every node of a 3D scene and lists
237
237
  | `summer_get_project_context` | Project, scene, and `.summer` memory summary — call at start of session. Binds the session to the open project and surfaces a `capabilitySkewWarning` when the engine build and CLI have drifted; tools whose op the engine provably lacks return a structured `engine_lacks_op` result instead of running. Compact by default (a few KB: `sceneSummary`, scalar `health` with capability counts); `include:['scene_tree','capabilities','settings']` adds the heavy blocks, `omitted` says how. |
238
238
  | `summer_get_agent_playbook` | Daily operating contract (observe-first loop, content routing, invariants, verification ritual) — call at start of session. Also served natively as the `summer_agent_playbook` MCP prompt. |
239
239
 
240
- ### Creator platform (3)
240
+ ### Creator platform (7)
241
241
 
242
242
  | Tool | Use |
243
243
  |---|---|
244
- | `summer_creator_publish` | Compute the exact `.pck` digest and size, require user confirmation, then run versioned prepare → write-once upload → finalize. The server independently verifies `publish` scope, ownership, bytes, and review state. |
244
+ | `summer_export_game` | Export the game in the file the Summer Games store takes, headless (no window), no running editor needed. `format:"bundle"` (default): the `summer.games` `.zip` the Summer apps run; `targets` picks `ios`, `android`, `macos`, `windows` (a game without a server: phones only) through a named `summer.games <targets>` preset in `export_presets.cfg`. `format:"download"` with one target: `web` (HTML5 `.zip` on Summer's WebGPU Forward+ template; refuses Compatibility projects), `macos` (ad hoc signed `.app` zip, `macos-universal`) or `windows` (`.exe` with embedded pack, zipped, `windows-x64`). Returns path, sha256, size, bundle manifest or store platform, warnings. |
245
+ | `summer_export_templates` | `list` or `install` export templates from Summer's CDN (`downloads.summer.games/engine-templates/<summerVersion>/manifest.json`) into the engine's user template folder (`<data>/Godot/export_templates/<FULL_CONFIG>/`), sha256-checked. Only download exports need them; store bundles need none. `summerVersion` is read on macOS, passed elsewhere. |
246
+ | `summer_publish_build` | Upload that `.zip` to the creator's game through Studio's store upload: confirm-gated preview first, then declare, part upload from disk, seal, wait for the Build, name its client pack; `publish:true` also approves it. Needs `summer login --store`; a retry with the same file and version continues the same upload. |
247
+ | `summer_creator_publish` | **Deprecated** (use the two tools above). Compute the exact `.pck` digest and size, require user confirmation, then run versioned prepare → write-once upload → finalize. The server independently verifies `publish` scope, ownership, bytes, and review state. |
245
248
  | `summer_creator_releases` | List real creator-owned releases from `summer.creator.v1`, with opaque cursor pagination. |
246
249
  | `summer_creator_config` | Read or confirm updates to the shared non-secret `~/.summer/config.json`. It never accepts or returns tokens. |
250
+ | `summer_capture_gameplay` | Real gameplay frames without a running editor: starts the game (main scene or `scene`) in the engine's offscreen verify instance (`--summer-verify`, real renderer, window parked offscreen, no focus) at `resolution` (default `1920x1080`), waits `waitSeconds`, saves `frames` PNGs (HUD included) to `<project>/.summer/captures/<time>/`. Imports the project first when `.godot/` is missing. Returns each path with its real width and height; warns when the stretch settings render another size. For store screenshots. |
247
251
 
248
252
  ### Library search (2)
249
253
 
@@ -1,6 +1,6 @@
1
1
  # Summer skills
2
2
 
3
- > 116 skills, grouped by their primary domain. **Generated** by `npm run generate:registry` from each skill's `resource.yaml`; `--check` fails when this file and the library disagree. Do not edit by hand.
3
+ > 118 skills, grouped by their primary domain. **Generated** by `npm run generate:registry` from each skill's `resource.yaml`; `--check` fails when this file and the library disagree. Do not edit by hand.
4
4
 
5
5
  The folder is flat on purpose (`docs/design/DECISIONS.md`, D3): a skill usually belongs to several domains, so categories live in each skill's `facets.domains` and this index is rendered from them. The first domain listed is the primary one. `summer skills list --by-domain` prints the same grouping; `summer skills info <slug>` shows one skill.
6
6
 
@@ -10,7 +10,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
10
10
 
11
11
  - [2d](#2d) (10)
12
12
  - [3d](#3d) (6)
13
- - [agent-workflow](#agent-workflow) (17)
13
+ - [agent-workflow](#agent-workflow) (18)
14
14
  - [ai](#ai) (2)
15
15
  - [animation](#animation) (7)
16
16
  - [assets](#assets) (3)
@@ -24,7 +24,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
24
24
  - [multiplayer](#multiplayer) (16)
25
25
  - [navigation](#navigation) (1)
26
26
  - [performance](#performance) (1)
27
- - [rendering](#rendering) (5)
27
+ - [rendering](#rendering) (6)
28
28
  - [runtime](#runtime) (1)
29
29
  - [scenes](#scenes) (12)
30
30
  - [scripting](#scripting) (2)
@@ -62,6 +62,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
62
62
 
63
63
  | skill | when to use | also |
64
64
  |---|---|---|
65
+ | [*bounded-engine-runs*](./bounded-engine-runs/SKILL.md) ★ (preview) | Run Summer headless or offscreen safely from an agent: time limits, one engine per project, muted runs, import passes, tests that fail on crashes. | headless, verification |
65
66
  | [brainstorming](./brainstorming/SKILL.md) ★ | Explore user intent, requirements, and design before implementation — must run before any creative work: features, components, mechanics, behavior. | game-design |
66
67
  | [debugging-game-feel](./debugging-game-feel/SKILL.md) | Debug features that work but feel wrong — floaty jumps, mushy combat, sluggish camera — subjective bugs living in tuning, timing, and feedback layers. | gameplay |
67
68
  | [diagnosing-perf-regressions](./diagnosing-perf-regressions/SKILL.md) | Find why frame rate, frame time, or load time got worse since a known-good state — regression hunting, not general performance tuning. | performance |
@@ -152,7 +153,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
152
153
  | [*celeste-momentum-platforming*](./celeste-momentum-platforming/SKILL.md) (preview) | Celeste-style 2D precision platformer movement — momentum running, coyote time, variable jumps, dash, wall jump/slide, climb stamina, pixel corner correction. | character-controller, game-design |
153
154
  | [*combat-basics*](./combat-basics/SKILL.md) (preview) | Damage in Summer Engine: hitbox and hurtbox Areas on opposing layers, a reusable Health component, damage signals, knockback, hit feedback, projectiles. | scripting, game-design |
154
155
  | [design-mechanic](./design-mechanic/SKILL.md) ★ | Design one game mechanic in detail — input, response, feedback, failure modes, depth, tunables — outputs a design doc, node-graph sketch, GDScript stub. | game-design |
155
- | [*in-game-purchases*](./in-game-purchases/SKILL.md) (preview) | Sell items for Sparks and let short players buy Sparks in game via the Summer purchase sheet: checkout, Sparks purchase, results, testing, store policy. | scripting, game-design |
156
+ | [*in-game-purchases*](./in-game-purchases/SKILL.md) (preview) | Sell items for Sparks in a game shop via the Summer purchase sheet: checkout, results, not enough Sparks, inventory unlocks, authority grants, store policy. | scripting, game-design |
156
157
  | [*web-and-touch-input*](./web-and-touch-input/SKILL.md) ★ (preview) | Input for the browser and phones: the first-click rule for sound and mouse capture, focus, and touch buttons or a virtual stick on the same actions. | ui |
157
158
 
158
159
  ## level-design
@@ -201,6 +202,7 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
201
202
  |---|---|---|
202
203
  | [3d-lighting](./3d-lighting/SKILL.md) ★ | Set up 3D scene lighting — DirectionalLight3D vs Omni vs Spot, WorldEnvironment, sky, shadow tuning, ambient — per current Summer conventions. | lighting |
203
204
  | [art-direction](./art-direction/SKILL.md) ★ | Define the game's visual style — references, palette, mood, lighting plan, post-processing, do/don't list — output as an art bible at .summer/art-bible.md. | lighting |
205
+ | [*compatibility-renderer-traps*](./compatibility-renderer-traps/SKILL.md) ★ (preview) | Make a Forward+ Summer game also look right on the Compatibility renderer: instance-uniform budget, black MultiMesh colors, sRGB vertex colors. | performance |
204
206
  | [*psx-retro-rendering*](./psx-retro-rendering/SKILL.md) (preview) | Hardware-informed PlayStation 1 rendering in Godot 4 — low-res output, RGB5 and exact dither, affine textures, vertex snapping, blend modes, fog; limits named. | 3d |
205
207
  | [*realtime-wet-surfaces*](./realtime-wet-surfaces/SKILL.md) (preview) | Real-time wetness on existing Godot 4 materials without losing their values — value-copying wet shader, geometry-driven wet mask, instance-uniform wet amount. | vfx |
206
208
  | [*web-compatibility-look*](./web-compatibility-look/SKILL.md) ★ (preview) | Make a web or browser game look high-end in the Compatibility renderer (WebGL 2): what works, what to avoid, 2D and 3D recipes, web performance. | 2d, 3d |
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: bounded-engine-runs
3
+ description: "Run Summer headless or offscreen safely from an agent: time limits, one engine per project, muted runs, import passes, tests that fail on crashes."
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: workflow
7
+ user-invocable: true
8
+ allowed-tools: Read Grep Glob Edit Write Bash
9
+ paths: ["project.godot", "tests/**/*.gd", "tools/**/*.gd"]
10
+ ---
11
+
12
+ # Bounded engine runs for agents
13
+
14
+ Agents verify games by running the engine many times: imports, tests, captures, multiplayer bots. Each run
15
+ that hangs, overlaps another, makes noise or hides an error costs time and trust. These rules come from a
16
+ day of several agents sharing one project on one Mac.
17
+
18
+ ## 1. Every run is bounded, muted and alone
19
+
20
+ - **Bound it.** macOS has no `timeout`. Wrap runs so the engine itself gets killed:
21
+ ```sh
22
+ perl -e 'alarm shift; exec @ARGV' 180 <summer> --headless --path . -s res://tests/run_tests.gd
23
+ ```
24
+ `alarm` survives `exec`; exit code 142 means the limit hit.
25
+ - **One engine per project at a time.** Two engines writing the same `.godot/` import cache corrupt it or
26
+ freeze each other's captures. Serialize with a lock file held by the engine process:
27
+ ```sh
28
+ perl -e 'use Fcntl qw(:flock); $^F=255; open(my $l,">>",shift) or die; flock($l,LOCK_EX); alarm shift; exec @ARGV' \
29
+ <project>/.engine.lock 180 <summer> ...
30
+ ```
31
+ Put this in one wrapper script and make every agent use it.
32
+ - **Cap its memory.** A time limit does not stop a run from eating all memory first. One day of several agents
33
+ running builds, renders and a browser load of a large web export at once kernel-panicked the Mac
34
+ (compressor full, swap unable to grow). The wrapper should run the command as a child, sum the resident
35
+ memory of its whole process tree (browsers spawn GPU/renderer helpers), kill the tree past a cap (e.g.
36
+ 3 GB), and also stop when system free memory falls low (`sysctl -n kern.memorystatus_level` < 15 on macOS),
37
+ because GPU memory on Apple Silicon is system memory but is not counted in RSS. Refuse to start when the
38
+ system is already short (< 35).
39
+ - **Mute it.** `--audio-driver Dummy`. Someone is working next to you.
40
+ - **Render only when you must, and offscreen.** `--headless` for logic (no pixels). For visual checks use
41
+ `--summer-offscreen` with a fixed `--resolution` and a capture script that saves PNGs and quits. Visible
42
+ windows only when a human asked to watch.
43
+
44
+ ## 2. Imports and parse errors
45
+
46
+ - New `class_name` scripts are unknown to `-s` scripts until an import pass registers them:
47
+ `<summer> --headless --audio-driver Dummy --path . --import`.
48
+ - The first import may rewrite `project.godot` (reorder keys, add a `[summer]` section; one trial saw a
49
+ hand-written `[rendering]` section dropped). Check `git diff project.godot` after it, and pass renderer
50
+ choices on the command line (`--rendering-method ...`) when a run depends on them.
51
+ - One broken `class_name` script cascades into "Could not parse global class" and dozens of unrelated
52
+ "cannot infer type" errors. Get the real line with:
53
+ `<summer> --headless --path . --check-only -s res://path/to/file.gd`
54
+ - Compile every script before booting the game: a tiny `SceneTree` script that `load()`s each `.gd` under
55
+ a folder and reports `can_instantiate()` failures, exit code = failures.
56
+
57
+ ## 3. Tests that can't lie
58
+
59
+ - A runtime script error inside a test does not fail it by default; the test just stops and counts as
60
+ passed. Register a logger and fail the test when its error count rises:
61
+ ```gdscript
62
+ class ErrorCounter extends Logger:
63
+ var script_errors := 0
64
+ func _log_error(_f: String, file: String, _line: int, _c: String, _r: String, _n: bool, type: int, _bt: Array[ScriptBacktrace]) -> void:
65
+ if type == Logger.ERROR_TYPE_SCRIPT or file.ends_with(".gd"):
66
+ script_errors += 1
67
+ func _log_message(_m: String, _e: bool) -> void:
68
+ pass
69
+ # in the runner: OS.add_logger(counter); compare counter.script_errors before and after each test
70
+ ```
71
+ - Exit 0 is not success. Always grep the log for `SCRIPT ERROR`, `Parse Error` and `ERROR:`, and print
72
+ one explicit success line (`TESTS passed=N failed=0`) you can check for.
73
+
74
+ ## 4. Typed GDScript traps agents hit repeatedly
75
+
76
+ - `var x := dict["k"]` cannot infer a type (Dictionary values are Variant). Write `var x: int = dict["k"]`.
77
+ - A loop variable that shadows an outer variable is a parse error that takes the whole class down.
78
+ - Don't name a class after an engine class (`SummerSession`); the error only says it "hides a native class".
79
+
80
+ ## 5. Web exports on a dev machine
81
+
82
+ - Check the `.pck` size before opening it in a browser; the browser holds all of it in memory. A project that
83
+ exports every imported asset of a large scene can pass 250 MB. Ship only what the game uses first.
84
+ - Load it in a browser only under the same memory-capped wrapper, one at a time, never next to other engine runs.
85
+
86
+ ## 6. Captures you can trust
87
+
88
+ - Read every PNG you produce. If several files are identical (`md5 -r *.png`), the run froze; rerun alone.
89
+ - Keep evidence outside the project (logs, PNGs) so imports never pick it up. If it must live inside, put an
90
+ empty `.gdignore` file in that folder so the editor and imports skip it.
91
+
92
+ ## See also
93
+
94
+ - `headless-scripting` (writing the SceneTree scripts these runs execute)
95
+ - `multiplayer-testing` (Local Play bot runs)
96
+ - `compatibility-renderer-traps` (dual-renderer captures)
@@ -0,0 +1,29 @@
1
+ id: skill/bounded-engine-runs
2
+ kind: skill
3
+ version: 1.0.0
4
+ summary: "Run Summer headless or offscreen safely from an agent: time limits, one engine per project, muted runs, import passes, tests that fail on crashes."
5
+ use_when:
6
+ - running the engine from a shell or agent for tests, imports, captures or bots
7
+ - several agents or processes share one project
8
+ - a run hangs, a capture freezes, or errors cascade across unrelated scripts
9
+ facets:
10
+ lifecycle:
11
+ - build
12
+ domains:
13
+ - agent-workflow
14
+ - headless
15
+ - verification
16
+ modalities:
17
+ - project
18
+ - scripts
19
+ compatibility:
20
+ engine: ">=4.6"
21
+ related:
22
+ skills:
23
+ - skill/headless-scripting
24
+ - skill/multiplayer-testing
25
+ - skill/compatibility-renderer-traps
26
+ source: official
27
+ license: MIT
28
+ status: preview
29
+ recommended: true