summer-engine 3.6.0 → 3.8.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 (83) 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 +24 -0
  7. package/README.md +11 -5
  8. package/commands/summer.md +2 -1
  9. package/dist/cli/commands/login.d.ts +0 -10
  10. package/dist/cli/commands/login.js +2 -82
  11. package/dist/cli/index.js +0 -4
  12. package/dist/core/auth.d.ts +0 -8
  13. package/dist/core/auth.js +5 -35
  14. package/dist/core/capabilities/apps.d.ts +318 -0
  15. package/dist/core/capabilities/apps.js +340 -0
  16. package/dist/core/capabilities/enable-plugin.d.ts +27 -0
  17. package/dist/core/capabilities/enable-plugin.js +75 -0
  18. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  19. package/dist/core/capabilities/seeing/seeing.d.ts +1 -1
  20. package/dist/core/capabilities/sfx-choices.d.ts +2 -15
  21. package/dist/core/capabilities/sfx-choices.js +26 -64
  22. package/dist/core/capabilities/tool-dispatch.js +51 -54
  23. package/dist/core/capability-skew.js +4 -0
  24. package/dist/core/config.d.ts +1 -7
  25. package/dist/core/config.js +5 -75
  26. package/dist/mcp/hosted-mount.js +13 -1
  27. package/dist/mcp/server.d.ts +2 -1
  28. package/dist/mcp/server.js +8 -5
  29. package/dist/mcp/tools/app-tools.d.ts +2 -0
  30. package/dist/mcp/tools/app-tools.js +64 -0
  31. package/dist/mcp/tools/creator-tools.js +0 -71
  32. package/dist/mcp/tools/generate-tools.js +6 -7
  33. package/gemini-extension.json +3 -1
  34. package/library/references/mcp-tools-reference/mcp-tools-reference.md +24 -5
  35. package/library/skills/README.md +10 -2
  36. package/library/skills/publish-your-game/SKILL.md +2 -0
  37. package/library/skills/sound-effect/SKILL.md +2 -0
  38. package/library/skills/summer-apps/SKILL.md +152 -0
  39. package/library/skills/summer-apps/manifest.md +57 -0
  40. package/library/skills/summer-apps/minimal-app.md +183 -0
  41. package/library/skills/summer-apps/resource.yaml +36 -0
  42. package/library/skills/summer-apps/sdk-reference.md +135 -0
  43. package/library/skills/summer-apps-design/SKILL.md +197 -0
  44. package/library/skills/summer-apps-design/resource.yaml +23 -0
  45. package/library/skills/using-summer/SKILL.md +1 -0
  46. package/library/tools/app-call/resource.yaml +59 -0
  47. package/library/tools/app-create/resource.yaml +67 -0
  48. package/library/tools/app-data/resource.yaml +74 -0
  49. package/library/tools/app-dock/resource.yaml +59 -0
  50. package/library/tools/app-events/resource.yaml +68 -0
  51. package/library/tools/app-get/resource.yaml +46 -0
  52. package/library/tools/app-guide/resource.yaml +55 -0
  53. package/library/tools/app-list/resource.yaml +48 -0
  54. package/library/tools/app-page/resource.yaml +74 -0
  55. package/library/tools/app-publish/resource.yaml +90 -0
  56. package/library/tools/app-report-gap/resource.yaml +58 -0
  57. package/library/tools/app-screenshot/resource.yaml +85 -0
  58. package/library/tools/app-tools/resource.yaml +46 -0
  59. package/library/tools/app-update/resource.yaml +68 -0
  60. package/library/tools/enable-plugin/resource.yaml +53 -0
  61. package/library/tools/generate-sfx-choices/resource.yaml +2 -2
  62. package/package.json +1 -1
  63. package/registry/generated/aliases.json +15 -3
  64. package/registry/generated/counts.json +3 -3
  65. package/registry/generated/gemini-extension.json +3 -1
  66. package/registry/generated/index.json +759 -209
  67. package/registry/generated/marketplace.claude.json +2 -2
  68. package/registry/generated/plugin.claude.json +4 -2
  69. package/registry/generated/plugin.codex.json +5 -3
  70. package/registry/generated/plugin.cursor.json +4 -2
  71. package/registry/generated/plugin.factory.json +4 -2
  72. package/registry/generated/skills-index.md +10 -2
  73. package/registry/generated/skills-registry.json +27 -0
  74. package/registry/schemas/allowed-hosts.json +4 -2
  75. package/dist/cli/commands/publish.d.ts +0 -2
  76. package/dist/cli/commands/publish.js +0 -21
  77. package/dist/cli/commands/releases.d.ts +0 -2
  78. package/dist/cli/commands/releases.js +0 -20
  79. package/dist/core/capabilities/creator.d.ts +0 -65
  80. package/dist/core/capabilities/creator.js +0 -374
  81. package/library/tools/creator-config/resource.yaml +0 -63
  82. package/library/tools/creator-publish/resource.yaml +0 -74
  83. package/library/tools/creator-releases/resource.yaml +0 -53
@@ -16,6 +16,7 @@ import { registerEventTools } from "./tools/event-tools.js";
16
16
  import { registerFabricateTools } from "./tools/fabricate-tools.js";
17
17
  import { registerUiTools } from "./tools/ui-tools.js";
18
18
  import { registerRuntimeTools } from "./tools/runtime-tools.js";
19
+ import { registerAppTools } from "./tools/app-tools.js";
19
20
  import { recordToolCall, registrationHasInputSchema, trajectoryArgsFor, } from "../core/trajectory.js";
20
21
  import { registerFileTools } from "./tools/file-tools.js";
21
22
  import { registerAssetTools } from "./tools/asset-tools.js";
@@ -40,14 +41,15 @@ export const getCachedBootDriftNotice = getCachedNotice;
40
41
  * turn and would otherwise learn the hard way. Kept under 600 characters so
41
42
  * it costs a few hundred tokens once; the full operating guide stays in the
42
43
  * summer_agent_playbook prompt / summer_get_agent_playbook tool. The last
43
- * sentence names the publishing path and the owner-approval rule.
44
+ * sentence names the publishing path, the owner-approval rule and where
45
+ * review feedback is read.
44
46
  */
45
- 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. " +
47
+ export const SUMMER_MCP_INSTRUCTIONS = "Summer Engine MCP. Call summer_get_project_context first: it binds the open project, returns mainScene, projectMemory, any capabilitySkewWarning. " +
46
48
  "Before building or fixing, summer_search_library then summer_read_library the hit. " +
47
- "After a playthrough read summer_get_diagnostics, not summer_get_console alone. " +
48
- "An all-black screenshot means the viewport had not redrawn: recapture. " +
49
+ "After play, read summer_get_diagnostics, not summer_get_console alone. " +
50
+ "An all-black screenshot means no redraw yet: recapture. " +
49
51
  "Report verified entries via summer_library_feedback. " +
50
- "Publishing: summer_export_game, summer_publish_build, summer_store_*; an agent never publishes alone: summer_store_submit returns the owner's approval link.";
52
+ "Publishing: summer_export_game, summer_publish_build, summer_store_*; agents never publish alone: summer_store_submit returns the owner's approval link; summer_store_review, feedback to fix.";
51
53
  /**
52
54
  * Fire-and-forget probe of the npm registry on MCP boot. Caches the result for
53
55
  * the session. We never await this in startup so a slow / offline registry
@@ -327,6 +329,7 @@ export function createMcpServer() {
327
329
  registerFabricateTools(server);
328
330
  registerUiTools(server);
329
331
  registerRuntimeTools(server);
332
+ registerAppTools(server);
330
333
  // The playbook is also an MCP prompt so prompt-surfacing hosts get it
331
334
  // natively (same content as the summer_get_agent_playbook tool).
332
335
  registerPlaybookPrompt(server);
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAppTools(server: McpServer): void;
@@ -0,0 +1,64 @@
1
+ import { MAX_APP_EVENTS_WAIT_SECONDS, APP_DOCK_FALLBACK, appCall, appCallArgsSchema, appCreate, appCreateArgsSchema, appData, appDataArgsSchema, appDockArgsSchema, appDockOp, appEvents, appEventsArgsSchema, appGet, appGetArgsSchema, appGuide, appGuideArgsSchema, appList, appListArgsSchema, appPage, appPageArgsSchema, appPublish, appPublishArgsSchema, appReportGap, appReportGapArgsSchema, appScreenshot, appScreenshotArgsSchema, appUpdate, appUpdateArgsSchema, appsGateway, appTools, appToolsArgsSchema, } from "../../core/capabilities/apps.js";
2
+ import { enablePluginArgsSchema, enablePluginOp, interpretEnablePluginResult } from "../../core/capabilities/enable-plugin.js";
3
+ import { missingEngineOpResult, withEngine } from "./with-engine.js";
4
+ /**
5
+ * Summer Apps over MCP: tools a game team uses inside Summer Studio, built by
6
+ * the agent. Shared implementation: core/capabilities/apps.ts.
7
+ */
8
+ const CLOUD = "Cloud tool: runs on Summer's servers and works without the Summer Engine app open. Requires sign-in: npx -y summer-engine@latest login.";
9
+ function result(value) {
10
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], ...(value.ok === false ? { isError: true } : {}) };
11
+ }
12
+ /** The MCP handler of one cloud tool: validate with the shared schema, call the web API. */
13
+ function cloud(name, schema, run) {
14
+ return async (args) => {
15
+ const parsed = schema.safeParse(args ?? {});
16
+ if (!parsed.success)
17
+ return result({ ok: false, code: "invalid", error: parsed.error.issues[0]?.message ?? "Invalid arguments" });
18
+ return result(await run(parsed.data, { request: appsGateway("mcp", name) }));
19
+ };
20
+ }
21
+ export function registerAppTools(server) {
22
+ server.tool("summer_app_guide", `Read this before building or changing a Summer App (a tool for the person's team inside Summer Studio, live on their assets, game data and engine): when to build one, Layout vs code, the build loop, manifest schema, SDK reference, UI kit, layout format, design rules and four worked examples. section returns one part.\n\n${CLOUD}`, appGuideArgsSchema.shape, cloud("summer_app_guide", appGuideArgsSchema, appGuide));
23
+ server.tool("summer_app_list", `The Summer Apps in the person's workspaces: id, name, purpose, folder, status (scratch, kept, archived), version, format.\n\n${CLOUD}`, appListArgsSchema.shape, cloud("summer_app_list", appListArgsSchema, appList));
24
+ server.tool("summer_app_create", `Create a new scratch Summer App; their open Summer Studio tab opens it. Without workspaceId it goes to the person's team (the one their Studio tab is on, else their latest team, else personal); the result's workspace says which: tell the person. Read summer_app_guide first. format code (index.html + JS/CSS) or layout (a JSON layout of kit nodes for a quick decision UI). Then summer_app_publish.\n\n${CLOUD}`, appCreateArgsSchema.shape, cloud("summer_app_create", appCreateArgsSchema, appCreate));
25
+ server.tool("summer_app_publish", `Publish a new immutable version of a Summer App: dir (a local folder with index.html at its root) or files [{path, content, encoding}] for code apps, layout for layout apps, plus the manifest. Limits: 10 MB, 300 files, 5 MB per file. The person's Studio tab boots it. An error names the file or rule to fix: fix it and publish again.\n\n${CLOUD}`, appPublishArgsSchema.shape, cloud("summer_app_publish", appPublishArgsSchema, appPublish));
26
+ server.tool("summer_app_get", `A Summer App's manifest, its current version's files (or layout) and a summary of its shared data. Use it before changing an app.\n\n${CLOUD}`, appGetArgsSchema.shape, cloud("summer_app_get", appGetArgsSchema, appGet));
27
+ server.tool("summer_app_update", `Rename a Summer App, change its purpose or folder, or set its status: kept (the team keeps it), archived (hidden from the app list; restore with scratch or kept), scratch. Archive the scratch apps you made that the person did not keep.\n\n${CLOUD}`, appUpdateArgsSchema.shape, cloud("summer_app_update", appUpdateArgsSchema, appUpdate));
28
+ server.tool("summer_app_events", `Wait for what a Summer App sends you: summer.agent.send events (kind agent), errors in the app (kind error), feedback (kind feedback), agent tool calls (kind tool). Waits up to timeoutSeconds (max ${MAX_APP_EVENTS_WAIT_SECONDS}) for the first event after the cursor; pass the returned cursor as after next time.\n\n${CLOUD}`, appEventsArgsSchema.shape, cloud("summer_app_events", appEventsArgsSchema, appEvents));
29
+ server.tool("summer_app_page", `Read or use the fields and buttons a Summer App declared while it is open in the person's Studio tab or editor dock: no fill and no press reads the page; fill [{field, value}] and press an action id use it. Critical actions (delete, publish, pay, send) are pressed only by the person. app_not_open when the app is not open.\n\n${CLOUD}`, appPageArgsSchema.shape, cloud("summer_app_page", appPageArgsSchema, appPage));
30
+ server.tool("summer_app_screenshot", `See a Summer App without the person: Summer renders its current version (or version) headless with the person's data and returns the picture plus what to fix: console lines, uncaught errors, refused calls, whether the SDK connected and rendered, and the fields, buttons and agent tools it declared. Read-only: writes, sends and paid calls are refused, nothing changes. width 390 for a phone, theme dark, fullPage for the whole scroll; out also saves the PNG. Use it after every publish.\n\n${CLOUD}`, appScreenshotArgsSchema.shape, async (args) => {
31
+ const parsed = appScreenshotArgsSchema.safeParse(args ?? {});
32
+ if (!parsed.success)
33
+ return result({ ok: false, code: "invalid", error: parsed.error.issues[0]?.message ?? "Invalid arguments" });
34
+ const shot = await appScreenshot(parsed.data, { request: appsGateway("mcp", "summer_app_screenshot") });
35
+ if (shot.ok !== true || !("image" in shot))
36
+ return result(shot);
37
+ const { image, ...report } = shot;
38
+ return { content: [{ type: "image", data: image.base64, mimeType: image.mimeType }, { type: "text", text: JSON.stringify(report, null, 2) }] };
39
+ });
40
+ server.tool("summer_app_data", `Read or write a Summer App's shared data, its API: works with no app open, and everyone in the app sees writes live. Without ops it reads records (paged with after) plus declaredCollections, the record shape the app declares; with ops it writes them in one revision: {op: put|patch|remove, collection, recordId, data|patch, ifRevision?}. Writes show as yours (actor kind agent); as: seed writes starter data for the team (kind seed, no author).\n\n${CLOUD}`, appDataArgsSchema.shape, cloud("summer_app_data", appDataArgsSchema, appData));
41
+ server.tool("summer_app_tools", `The agent tools (name, description, input JSON Schema) and the data collections (record fields and types) a Summer App declares. Run a tool with summer_app_call; read or write the collections with summer_app_data.\n\n${CLOUD}`, appToolsArgsSchema.shape, cloud("summer_app_tools", appToolsArgsSchema, appTools));
42
+ server.tool("summer_app_call", `Run one of a Summer App's agent tools inside the person's open app (Studio tab or editor dock) with their rights; waits up to 60 s. app_not_open when the app is not open: use summer_app_data or ask the person to open it. May spend the person's credits when the tool generates.\n\n${CLOUD}`, appCallArgsSchema.shape, cloud("summer_app_call", appCallArgsSchema, appCall));
43
+ server.tool("summer_app_report_gap", `Tell Summer about an SDK capability a Summer App needed and did not have, so it can be built for everyone.\n\n${CLOUD}`, appReportGapArgsSchema.shape, cloud("summer_app_report_gap", appReportGapArgsSchema, appReportGap));
44
+ server.tool("summer_app_dock", `Dock a Summer App as a panel in the running Summer Engine editor (left, right or bottom), close its dock, or list docked apps. Docks persist per project. Engine builds without app docks return engine_lacks_op: open the app in the Summer Studio tab instead.`, appDockArgsSchema.shape, async (args) => {
45
+ const parsed = appDockArgsSchema.safeParse(args ?? {});
46
+ if (!parsed.success)
47
+ return result({ ok: false, code: "invalid", error: parsed.error.issues[0]?.message ?? "Invalid arguments" });
48
+ if (parsed.data.action !== "list" && !parsed.data.appId)
49
+ return result({ ok: false, code: "invalid", error: `${parsed.data.action} needs appId` });
50
+ const op = appDockOp(parsed.data);
51
+ return withEngine(async (client) => {
52
+ const missing = missingEngineOpResult(client, op.op, APP_DOCK_FALLBACK);
53
+ if (missing)
54
+ return missing;
55
+ return client.executeOps([op]);
56
+ });
57
+ });
58
+ server.tool("summer_enable_plugin", `Enable an editor plugin in the open project (res://addons/<name>/, for example one paired with a Summer App) and report the real result: enabled with the autoloads it registered, or why it failed (code SCRIPT_ERROR, NOT_TOOL, NOT_EDITOR_PLUGIN, CFG_MISSING, TIMEOUT, ...). Older Summer Engine builds only queue the enable; then the result says queued: true and enabled: null, and you check summer_get_debugger_errors before relying on the plugin.`, enablePluginArgsSchema.shape, async (args) => {
59
+ const parsed = enablePluginArgsSchema.safeParse(args ?? {});
60
+ if (!parsed.success)
61
+ return result({ ok: false, code: "invalid", error: parsed.error.issues[0]?.message ?? "Invalid arguments" });
62
+ return withEngine(async (client) => interpretEnablePluginResult(await client.executeOps([enablePluginOp(parsed.data)])));
63
+ });
64
+ }
@@ -1,27 +1,16 @@
1
1
  import { z } from "zod";
2
- import { CreatorOperationError, listCreatorReleases, publishCreator, } from "../../core/capabilities/creator.js";
3
2
  import { captureGameplay } from "../../core/capabilities/capture-gameplay.js";
4
3
  import { exportGame } from "../../core/capabilities/export-game.js";
5
4
  import { STORE_TARGETS } from "../../core/capabilities/export-presets.js";
6
5
  import { exportTemplates, TEMPLATE_PLATFORMS } from "../../core/capabilities/export-templates.js";
7
6
  import { publishBuild } from "../../core/capabilities/publish-build.js";
8
7
  import { BuildToolError } from "../../core/capabilities/summer-bundle.js";
9
- import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, setConfigValue, unsetConfigValue, } from "../../core/config.js";
10
8
  import { textJson } from "./text-json.js";
11
9
  async function creatorResult(operation) {
12
10
  try {
13
11
  return textJson(await operation());
14
12
  }
15
13
  catch (error) {
16
- if (error instanceof CreatorOperationError) {
17
- return textJson({
18
- ok: false,
19
- code: error.code,
20
- operation: error.operation,
21
- message: error.message,
22
- recovery: error.recovery,
23
- }, true);
24
- }
25
14
  if (error instanceof BuildToolError) {
26
15
  return textJson({
27
16
  ok: false,
@@ -116,64 +105,4 @@ export function registerCreatorTools(server) {
116
105
  confirm: z.boolean().default(false).describe("Set true only after the user approves the exact upload."),
117
106
  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."),
118
107
  }, async (args) => creatorResult(() => publishBuild({ ...args, face: "mcp" })));
119
- 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.", {
120
- project: z.string().optional().describe("Project root. Defaults to the current working directory."),
121
- artifact: z.string().describe("Exact path to the exported Summer .pck artifact."),
122
- version: z.string().describe("Immutable release version."),
123
- manifest: z.string().optional().describe("Optional path to a JSON release manifest."),
124
- projectId: z.string().optional().describe("Creator project ID. Defaults to ~/.summer/config.json."),
125
- channel: z.string().optional().describe("Release channel. Defaults to the configured channel or production."),
126
- notes: z.string().optional().describe("Release notes shown during confirmation and recorded in the local audit."),
127
- confirm: z.boolean().default(false).describe("Set true only after the user approves the exact release target."),
128
- }, async (args) => creatorResult(() => publishCreator({
129
- ...args,
130
- face: "mcp",
131
- })));
132
- server.tool("summer_creator_releases", "Deprecated: lists releases on the legacy Summercraft creator API (separate sc_ token), not summer.games. For what players get on summer.games, and which upload can be released, use summer_store_releases.", {
133
- projectId: z.string().optional().describe("Creator project ID. Defaults to ~/.summer/config.json."),
134
- limit: z.number().int().min(1).max(100).default(20),
135
- cursor: z.string().optional().describe("Opaque nextCursor from the previous page."),
136
- }, async (args) => creatorResult(() => listCreatorReleases({
137
- ...args,
138
- face: "mcp",
139
- })));
140
- server.tool("summer_creator_config", "Read or update the shared non-secret ~/.summer configuration used by the CLI and this MCP. Mutations require confirm=true. Tokens are never returned or accepted by this tool.", {
141
- action: z.enum(["list", "get", "set", "unset"]),
142
- key: z.enum(CONFIG_KEYS).optional(),
143
- value: z.string().optional(),
144
- confirm: z.boolean().default(false),
145
- }, async ({ action, key, value, confirm }) => creatorResult(async () => {
146
- if (action === "list") {
147
- const config = await readSummerConfig();
148
- return {
149
- ok: true,
150
- values: Object.fromEntries(CONFIG_KEYS.map((name) => [
151
- name,
152
- getConfigValue(config, name) ?? null,
153
- ])),
154
- };
155
- }
156
- if (!key || !isConfigKey(key)) {
157
- throw new Error(`A valid key is required. Recovery: use one of ${CONFIG_KEYS.join(", ")}.`);
158
- }
159
- if (action === "get") {
160
- return {
161
- ok: true,
162
- key,
163
- value: getConfigValue(await readSummerConfig(), key) ?? null,
164
- };
165
- }
166
- if (!confirm) {
167
- throw new Error(`Changing ${key} requires confirmation. Recovery: show the exact change to the user, then retry with confirm=true.`);
168
- }
169
- if (action === "set") {
170
- if (value === undefined) {
171
- throw new Error(`A value is required for ${key}. Recovery: provide value and retry with confirm=true.`);
172
- }
173
- const config = await setConfigValue(key, value);
174
- return { ok: true, key, value: getConfigValue(config, key) };
175
- }
176
- const config = await unsetConfigValue(key);
177
- return { ok: true, key, value: getConfigValue(config, key) ?? null };
178
- }));
179
108
  }
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { BOARD_ENDPOINT, boardArgsSchema, boardContent, boardQuery } from "../../core/capabilities/board.js";
3
3
  import { IMAGE_GENERATION_TIMEOUT_MS, imageGenerationArgsSchema, saveGeneratedImage } from "../../core/capabilities/image-generation.js";
4
4
  import { buildMotionRequestBody, motionErrorHint, motionGenerationArgsSchema, motionJobFailureHint, } from "../../core/capabilities/motion-generation.js";
5
- import { MAX_CHOICES_WAIT_SECONDS, defaultCanOpenBrowser, generateSfxChoices, getChoices, getChoicesArgsSchema, openInStudioTab, sfxChoicesArgsSchema, SFX_CHOICES_ENDPOINT, } from "../../core/capabilities/sfx-choices.js";
5
+ import { MAX_CHOICES_WAIT_SECONDS, defaultCanOpenBrowser, generateSfxChoices, getChoices, getChoicesArgsSchema, sfxChoicesArgsSchema, SFX_CHOICES_ENDPOINT, } from "../../core/capabilities/sfx-choices.js";
6
6
  import open from "open";
7
7
  import { getAuthToken } from "../../core/auth.js";
8
8
  import { resolveGatewayUrl } from "../../core/config.js";
@@ -106,9 +106,9 @@ async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
106
106
  if (!res.ok) {
107
107
  let message = formattedApiErrorMessage(res.status, data);
108
108
  if (res.status === 402) {
109
- message =
110
- (data.message || "Insufficient credits.") +
111
- "\nTop up at: https://www.summerengine.com/dashboard\nOr upgrade your plan at: https://www.summerengine.com/pricing";
109
+ // One link: the Add credits step in Studio (no plan needed). Newer servers already put it in the message.
110
+ const base = String(data.message || "Not enough credits.");
111
+ message = base.includes("/studio/credits") ? base : `${base}\nAdd credits: https://summerengine.com/studio/credits`;
112
112
  }
113
113
  if (res.status === 401) {
114
114
  message =
@@ -453,7 +453,6 @@ Requires authentication: run 'npx -y summer-engine@latest login' first.`, {
453
453
  const sfxChoicesDeps = {
454
454
  post: (endpoint, body, timeoutMs) => mcpGenerate(endpoint, body, timeoutMs),
455
455
  get: (endpoint, params, timeoutMs) => mcpGet(endpoint, params, timeoutMs),
456
- studioOpen: (path, reviewUrl) => openInStudioTab(path, reviewUrl),
457
456
  openUrl: (url) => open(url),
458
457
  canOpenBrowser: () => defaultCanOpenBrowser(),
459
458
  };
@@ -463,8 +462,8 @@ one or more. The approved model becomes their default for sound effects.
463
462
 
464
463
  Use it for the first sound effect in a chat after the person said yes to
465
464
  "three versions to pick from", or whenever they want to compare. The page
466
- opens in their open Summer Studio tab when there is one (needs the store
467
- sign-in), else in the browser; open:false only returns reviewUrl.
465
+ opens in their open Summer Studio tab when there is one, else in the browser
466
+ (open:false skips the browser and only returns reviewUrl).
468
467
 
469
468
  Then tell the person the returned "say" text and call summer_get_choices
470
469
  with the choiceSetId. Do not pick for them.
@@ -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.6.0",
4
+ "version": "3.8.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": {
@@ -113,6 +113,8 @@
113
113
  "./library/skills/store-art/",
114
114
  "./library/skills/store-listing/",
115
115
  "./library/skills/summer-analytics/",
116
+ "./library/skills/summer-apps/",
117
+ "./library/skills/summer-apps-design/",
116
118
  "./library/skills/summer-friends/",
117
119
  "./library/skills/summer-leaderboards/",
118
120
  "./library/skills/summer-match-results/",
@@ -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 (107 tools)
20
+ ## Tool surface (119 tools)
21
21
 
22
22
  ### Project files (4)
23
23
 
@@ -240,16 +240,13 @@ Preview. One fast, read-only call that walks every node of a 3D scene and lists
240
240
  | `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. |
241
241
  | `summer_get_board` | Read the person's approved planning board (look, characters, place, storyboard, picks), with the look and picked cards as images to compare screenshots against. Needs the project id from the build brief; works without the engine. |
242
242
 
243
- ### Creator platform (7)
243
+ ### Creator platform (4)
244
244
 
245
245
  | Tool | Use |
246
246
  |---|---|
247
247
  | `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. |
248
248
  | `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. |
249
249
  | `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. |
250
- | `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. |
251
- | `summer_creator_releases` | **Deprecated.** Lists releases on the legacy creator API. For summer.games use `summer_store_releases` (hosted, mounted after `summer login --store`). |
252
- | `summer_creator_config` | Read or confirm updates to the shared non-secret `~/.summer/config.json`. It never accepts or returns tokens. |
253
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) (`--summer-offscreen`) at `resolution` (default `1920x1080`), waits `waitSeconds`, runs `steps` (`press` a button by text, `key`, `action`, `click`, `drag`, `wait`, `shot`), saves `frames` PNGs (HUD included; with a `shot` step, one per shot) to `<project>/.summer/captures/<time>/`. `args` are the game's own flags, passed after `--` (e.g. `["--autostart"]` to skip the title screen). 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. |
254
251
 
255
252
  ### Library search (2)
@@ -265,6 +262,28 @@ Preview. One fast, read-only call that walks every node of a 3D scene and lists
265
262
  |---|---|
266
263
  | `summer_library_feedback` | Report library-entry outcomes (worked/wrong/outdated/...) so entries get fixed and re-ranked. Fire-and-forget with a 1s cap; enum-first schema with no field for project files, chat content, or code; honors `SUMMER_NO_TELEMETRY=1` and `DO_NOT_TRACK=1`. |
267
264
 
265
+ ### Summer Apps (13)
266
+
267
+ Team tools inside Summer Studio that agents build: shared, live on the team's data and engine. Load the `summer-apps` and `summer-apps-design` skills first.
268
+
269
+ | Tool | Use |
270
+ |---|---|
271
+ | `summer_app_guide` | The build guide: when to build, Layout vs code, build loop, manifest, SDK, UI kit, layout format, design rules, worked examples. |
272
+ | `summer_app_list` | The apps in the person's workspaces. |
273
+ | `summer_app_create` | New scratch app; the person's Studio tab opens it. |
274
+ | `summer_app_publish` | New immutable version from a local `dir`, `files` or a `layout`, plus the manifest. |
275
+ | `summer_app_get` | Manifest, current files and a data summary. |
276
+ | `summer_app_update` | Rename, move to a folder, keep, archive or restore an app. |
277
+ | `summer_app_events` | Long-poll (up to 50 s) what the app sends agents, errors, feedback and tool calls. |
278
+ | `summer_app_page` | Read, fill and press the open app's declared fields and buttons. |
279
+ | `summer_app_screenshot` | See the app without the person: a headless, read-only render (PNG) with console, errors, refused calls and the declared page; `width` 390 for a phone, `theme` dark, `out` saves the PNG. |
280
+ | `summer_app_data` | Read or write the app's shared collections; no app needs to be open, the team sees writes live. |
281
+ | `summer_app_tools` | The app's declared agent tools. |
282
+ | `summer_app_call` | Run an agent tool inside the person's open app; `app_not_open` otherwise. |
283
+ | `summer_app_report_gap` | Report an SDK capability an app needed and did not have. |
284
+ | `summer_app_dock` | Dock an app in the editor (left, right, bottom), close it, list docks; `engine_lacks_op` on engines without app docks. |
285
+ | `summer_enable_plugin` | Enable an editor plugin (`res://addons/<name>/`) and get the real load result (`enabled`, autoloads, or a failure `code`); older engines answer `queued: true, enabled: null` instead of claiming success. |
286
+
268
287
  ## Common pattern
269
288
 
270
289
  Every scene-touching skill should follow this loop:
@@ -1,6 +1,6 @@
1
1
  # Summer skills
2
2
 
3
- > 123 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
+ > 125 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
 
@@ -29,7 +29,8 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
29
29
  - [runtime](#runtime) (1)
30
30
  - [scenes](#scenes) (12)
31
31
  - [scripting](#scripting) (2)
32
- - [ui](#ui) (1)
32
+ - [tools](#tools) (1)
33
+ - [ui](#ui) (2)
33
34
  - [vfx](#vfx) (9)
34
35
  - [video](#video) (3)
35
36
 
@@ -248,10 +249,17 @@ Status: **stable** unless marked; *preview* = not yet exercised in-engine by the
248
249
  | [gdscript-patterns](./gdscript-patterns/SKILL.md) ★ | GDScript conventions — type hints, signals, exports, onready, lifecycle methods, get_node vs $NodePath, naming. | conventions |
249
250
  | [*save-load*](./save-load/SKILL.md) (preview) | Persist game state in Summer Engine: user:// paths, JSON progress vs ConfigFile settings, a versioned save schema with migration, autosave hooks. | gameplay |
250
251
 
252
+ ## tools
253
+
254
+ | skill | when to use | also |
255
+ |---|---|---|
256
+ | [*summer-apps*](./summer-apps/SKILL.md) ★ (preview) | Build a Summer App, a team tool inside Summer Studio (balance table, quest editor, tracker, pick grid): build loop, Layout vs code, data as API, agent tools. | ui, agent-workflow |
257
+
251
258
  ## ui
252
259
 
253
260
  | skill | when to use | also |
254
261
  |---|---|---|
262
+ | [*summer-apps-design*](./summer-apps-design/SKILL.md) ★ (preview) | How a Summer App should look - page layout, Summer UI kit components, tables, charts, media, boards, states and a pre-ship checklist. | tools |
255
263
  | [ui-basics](./ui-basics/SKILL.md) ★ | Build Summer Engine UI — HUDs, menus, health bars, dialogue boxes, Control trees — anchors, containers, responsive layout, theme vs inline styling. | ux |
256
264
 
257
265
  ## vfx
@@ -21,3 +21,5 @@ Its current text lives on the hosted Summer Engine MCP, the same text the Studio
21
21
  Before the build upload, run `release-readiness` on the exact `.zip` you upload, and report each check to the owner; a hosted game's upload checks can pass while every online join fails.
22
22
 
23
23
  The one rule to keep even before you load it: Submitting does not publish. `summer_store_submit` returns an approval link; only the owner's click sends the game to review, and review comes before live.
24
+
25
+ After the review: `summer_store_review` (hosted) returns Summer's decision, the reviewer's feedback and the next step. When Summer asks for changes or does not approve the game, fix every point in the feedback, then call `summer_store_submit` again; the owner approves the resubmission. If there is no written feedback, say so and ask the owner instead of guessing. The game is not live until Summer approves it.
@@ -106,6 +106,8 @@ summer_generate_audio(capability: "sound_effects", text: "<prompt>", durationSec
106
106
 
107
107
  Leave out `modelId` so their approved default runs. The description goes in `text`, not `prompt` (`prompt` gives a 400 `text_required` here).
108
108
 
109
+ No Summer project (an asset-only request, or the engine is not installed or not running): stop here. The clips are in the person's Summer library; give them the `audioUrl` of each approved version and skip steps 7 and 8.
110
+
109
111
  ### 7. Import
110
112
 
111
113
  ```
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: summer-apps
3
+ description: "Build a Summer App, a team tool inside Summer Studio (balance table, quest editor, tracker, pick grid): build loop, Layout vs code, data as API, agent tools."
4
+ license: MIT
5
+ compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
+ category: tools
7
+ user-invocable: true
8
+ allowed-tools: Read Write Edit Glob summer_app_guide summer_app_list summer_app_create summer_app_publish summer_app_get summer_app_update summer_app_events summer_app_page summer_app_screenshot summer_app_data summer_app_tools summer_app_call summer_app_dock summer_app_report_gap
9
+ ---
10
+
11
+ # Summer Apps
12
+
13
+ A Summer App is a tool the person's team uses inside Summer Studio: shared with
14
+ the workspace, live on its assets, game data and engine, dockable in the editor.
15
+ You build it in minutes; the team keeps it.
16
+
17
+ ## First: does Summer already do it?
18
+
19
+ A custom app is something the team has to maintain. Before you build one, check whether
20
+ Summer already does the job: Studio features (the asset library with its collections and
21
+ groups, Pick one and sound choices, Grow dashboards, Team) and the existing `summer_*` MCP
22
+ tools. If it does, use that and tell the person where it lives.
23
+
24
+ Build an app only when the need is real and Summer lacks it, or as a Scratch throwaway (a
25
+ quick asset review, a UI prototype) that you call throwaway when you make it, and archive
26
+ when it is done (`summer_app_update {appId, status: "archived"}`). When
27
+ something is missing natively, report it with `summer_app_report_gap` (and
28
+ `summer_library_feedback` when a library entry fell short), so Summer can build it for
29
+ everyone.
30
+
31
+ | Ask | Do |
32
+ | --- | --- |
33
+ | "Let me pick from these 12 sword sounds" | Pick one: `summer_generate_sfx_choices`, or put the sounds in a library collection. No app. |
34
+ | "Group our Studio assets into a moodboard for the forest level" | Organize them with the asset library's collections and groups. No app. |
35
+ | "A weapon balance table that writes res://data/weapons.json" | Build an app: nothing in Summer edits game data with the team's balance rules. |
36
+
37
+ ## When to build one
38
+
39
+ - The person asks for a tool ("build us a loot balance tool", "track every creature
40
+ from concept to in-engine", "a board to review playtest clips").
41
+ - They need a UI to decide or edit something they will come back to.
42
+ - Not for a one-off answer: reply in chat instead.
43
+ - Not when Summer already does it (above).
44
+
45
+ ## Layout or code
46
+
47
+ - **Layout** (`format: "layout"`): a JSON layout of kit nodes. Quick, throwaway,
48
+ always on brand. Use it to ask the person to pick, rate or fill something now.
49
+ - **Code** (default): `index.html` + JS/CSS with the Summer SDK and UI kit. Use it
50
+ for state, editing, shared data, generation or engine access.
51
+
52
+ ## Start from the working app
53
+
54
+ Read these before writing code; they are exact:
55
+
56
+ - [minimal-app.md](minimal-app.md): a complete working app (manifest, `index.html`,
57
+ `app.js`) built from kit components, using shared data, live changes, the viewer, `page.declare`, an
58
+ agent event, an agent tool and frame-move state. Copy it and change it.
59
+ - [sdk-reference.md](sdk-reference.md): every SDK call with its exact arguments and
60
+ return shape.
61
+ - [manifest.md](manifest.md): every manifest field.
62
+ - The kit's component catalog (every `<summer-*>` element with attributes, events and a
63
+ copyable example) is `summer_app_guide` section `kit`, also at `/summer-ui/v1/catalog.json`
64
+ on the app host. Copy its examples instead of inventing markup.
65
+
66
+ To see an app, publish it and call `summer_app_screenshot {appId}`: Summer renders it headless
67
+ with the person's data and returns the picture plus the console, uncaught errors, refused calls
68
+ and the fields and buttons it declared. No Studio tab needed; the person sees it at
69
+ `/studio/apps/<appId>` (`summer_app_create` opens it for them). Opening `index.html` on its own
70
+ never connects. There is no offline host: do not build one or mock the SDK.
71
+
72
+ The app is served from its own host, apps.summerengine.app. Load the SDK and the kit as
73
+ ES modules with root paths: `<script type="module" src="/summer-sdk/v1.js">` and
74
+ `<script type="module" src="/summer-ui/v1/ui.js">`. Then `await summer.ready` before
75
+ the first call, and reach data only through `summer.data.collection(name)`.
76
+
77
+ ## The loop
78
+
79
+ 1. `summer_app_guide` (whole once per session; later one `section`). Load the
80
+ `summer-apps-design` skill for how it must look.
81
+ 2. `summer_app_create {name, purpose, format}`. The person's Studio tab opens it.
82
+ 3. Write the files in a local folder, then `summer_app_publish {appId, dir, manifest, note}`
83
+ (or `layout`). Each publish is an immutable version the tab boots. A publish
84
+ error names the file or rule: fix it and publish again.
85
+ 4. Seed starter data with `summer_app_data` (`ops` plus `"as": "seed"`) when the app should
86
+ start full. Seed rows belong to the team and show no author.
87
+ 5. Look at it: `summer_app_screenshot {appId}`, then `width: 390` (phone) and `theme: "dark"`.
88
+ Fix every error and console line it reports, publish again, look again. It is read-only:
89
+ writes, sends and paid calls are refused there and work in Studio.
90
+ 6. `summer_app_events {appId, after}` waits for what the app sends you
91
+ (`summer.agent.send`), errors (uncaught and `console.error`) and feedback. Act on them;
92
+ pass the cursor on.
93
+ 7. Change the app by publishing again; `summer_app_get` returns the current files.
94
+
95
+ ## Data is the API
96
+
97
+ Read and write an app's shared collections with `summer_app_data`. It works with
98
+ no app open, and everyone in the app sees your edits live. "Nerf the bow in our
99
+ balance sheet" is a data write, not a click.
100
+
101
+ Declare each collection's record shape in the manifest `collections`; `summer_app_tools`
102
+ and `summer_app_data` reads return it, so any agent knows what a record looks like.
103
+
104
+ ```json
105
+ { "appId": "<id>", "ops": [
106
+ { "op": "patch", "collection": "items", "recordId": "bow", "patch": { "damage": 36 }, "ifRevision": 4 },
107
+ { "op": "put", "collection": "notes", "recordId": "n-17", "data": { "text": "Boss too long", "status": "open" } },
108
+ { "op": "remove", "collection": "notes", "recordId": "n-3" }
109
+ ] }
110
+ ```
111
+
112
+ Up to 100 ops per call. Without `ops`, `summer_app_data` reads (`collection`, `after`, `limit`);
113
+ removed records are not returned. Your writes are recorded as `actor { kind: "agent" }` (and
114
+ as `createdBy` on records you create), so the app can show which rows an agent made. Add
115
+ `"as": "seed"` for starter data that belongs to the team (`kind: "seed"`, no author). `patch` merges keys
116
+ (`null` removes one); `ifRevision` guards against overwriting a newer change.
117
+
118
+ ## Agent tools
119
+
120
+ Logic that lives in the app (a balance formula, an export) can be exposed in the
121
+ manifest `agentTools` and implemented with `summer.agent.tool(name, handler)`.
122
+ `summer_app_tools` lists them; `summer_app_call` runs one inside the person's open
123
+ app and answers `app_not_open` otherwise. Prefer data for plain reads and edits.
124
+
125
+ ## Rules
126
+
127
+ - Declare only the permissions the app uses; declare every `engineFiles` path and
128
+ every `agentEvents` name. A missing one fails at runtime with `permission_missing`.
129
+ - The app runs sandboxed (`allow-scripts allow-forms allow-popups allow-modals
130
+ allow-downloads`, never `allow-same-origin`): no cookies, no `localStorage`, no network
131
+ unless the workspace allows a domain. Keep state in `summer.data`.
132
+ - Paid SDK calls spend the credits of the person using the app. Say so before
133
+ building an app that generates.
134
+ - Use real data (`summer.data`, `summer.assets`, `summer.engine.readFile`), never
135
+ placeholder numbers.
136
+ - Studio moves an idle app to a fresh frame every few minutes. Keep UI state you care
137
+ about in shared data, or return it from `summer.app.onSnapshot(() => state)` (up to
138
+ 256 KB) and read it back from `(await summer.ready).restored`. Wrap Web Audio playback
139
+ in `summer.app.busy(true)` / `summer.app.busy(false)` so a long sound is not cut;
140
+ `<audio>` and `<video>` count on their own.
141
+ - Call `summer.page.declare` so agents can read and use the UI; mark delete,
142
+ publish, pay and send actions `critical`.
143
+ - When the SDK cannot do what the app needs, say so and call
144
+ `summer_app_report_gap`.
145
+ - `summer_app_dock` docks an app in the editor; on engines without app docks it
146
+ returns `engine_lacks_op`: open the app in the Studio tab instead.
147
+
148
+ ## Done means
149
+
150
+ The app is published; `summer_app_screenshot` shows it with real data in light, dark and at
151
+ 390 px with no errors; its states (loading, empty, error) work; and the person knows what it
152
+ does and where its data goes.
@@ -0,0 +1,57 @@
1
+ # The Summer App manifest
2
+
3
+ Sent as `manifest` on every `summer_app_publish` and written into the app as
4
+ `summer.app.json`. Unknown keys and unknown permissions are refused with a message that
5
+ names them. Every field:
6
+
7
+ ```json
8
+ {
9
+ "name": "Loot table",
10
+ "purpose": "Balance item stats and write them to res://data/items.json",
11
+ "permissions": ["data", "assets:read", "generate", "ai", "team:read", "engine:read", "engine:write"],
12
+ "engineFiles": ["res://data/items.json"],
13
+ "collections": {
14
+ "items": {
15
+ "description": "One weapon or armour piece",
16
+ "fields": {
17
+ "name": { "type": "string", "required": true },
18
+ "damage": { "type": "number", "description": "Damage per hit" },
19
+ "speed": { "type": "number", "description": "Attacks per second" },
20
+ "tags": { "type": "array" }
21
+ }
22
+ }
23
+ },
24
+ "agentEvents": ["items.written"],
25
+ "agentTools": [
26
+ {
27
+ "name": "nerf_item",
28
+ "description": "Lower one item's damage by a percent",
29
+ "input": {
30
+ "type": "object",
31
+ "properties": { "id": { "type": "string" }, "percent": { "type": "number" } },
32
+ "required": ["id", "percent"]
33
+ }
34
+ }
35
+ ],
36
+ "surface": "both",
37
+ "dock": { "preferred": "bottom" },
38
+ "plugin": { "name": "loot_tools" }
39
+ }
40
+ ```
41
+
42
+ | Key | Rules |
43
+ |---|---|
44
+ | `name` | Required, 1-80 characters. |
45
+ | `purpose` | Up to 280 characters: what the tool does and where its data goes. |
46
+ | `permissions` | Only these: `data`, `assets:read`, `assets:write`, `generate`, `ai`, `projects:read`, `team:read`, `grow:read`, `engine:read`, `engine:write`, `choices`. `engine:write` includes `engine:read`; `assets:write` includes `assets:read`. No permission is needed for `app.*`, `agent.*`, `page.*` and `team.presence`. |
47
+ | `engineFiles` | Up to 50 `res://` paths `engine.readFile` / `writeFile` may touch. |
48
+ | `collections` | Up to 50, keyed by collection name (lowercase `a-z 0-9 _ . -`). Each: `description`, `fields` (up to 50, name `[A-Za-z_][A-Za-z0-9_]*`) with `type` (`string`, `number`, `integer`, `boolean`, `object`, `array`, `any`), `description`, `required`. Descriptive: agents and people read it; writes are not rejected for it. |
49
+ | `agentEvents` | Up to 50 names (`a-z 0-9 _ . -`) the app sends with `summer.agent.send`. |
50
+ | `agentTools` | Up to 20: `name` (`a-z 0-9 _`, max 64), `description` (max 1,000), `input` (JSON Schema, up to 8 KB). |
51
+ | `surface` | `page` (the Studio app page, default), `dock` (an editor dock) or `both`. A dock layout must work from 320 px wide; docked apps get selection and scene events (`summer.engine.onEvent`). |
52
+ | `dock` | `{ "preferred": "left" \| "right" \| "bottom" }`. |
53
+ | `plugin` | `{ "name": "<folder>" }`: the app may also write `res://addons/<folder>/`. |
54
+ | `entry` | Always `index.html` (you can leave it out). |
55
+
56
+ The whole manifest is at most 200 KB. A code app's bundle: `index.html` at the root, at most
57
+ 300 files, 10 MB, 5 MB per file.