summer-engine 4.3.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -1
  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/AGENTS.md +2 -2
  7. package/CHANGELOG.md +100 -0
  8. package/README.md +67 -27
  9. package/commands/start.md +19 -15
  10. package/commands/summer.md +1 -0
  11. package/dist/cli/command-usage.d.ts +3 -1
  12. package/dist/cli/command-usage.js +10 -4
  13. package/dist/cli/commands/create.d.ts +1 -25
  14. package/dist/cli/commands/create.js +10 -138
  15. package/dist/cli/commands/dev.d.ts +66 -0
  16. package/dist/cli/commands/dev.js +234 -0
  17. package/dist/cli/commands/install.d.ts +15 -58
  18. package/dist/cli/commands/install.js +31 -481
  19. package/dist/cli/commands/login.js +1 -1
  20. package/dist/cli/commands/ops.d.ts +17 -1
  21. package/dist/cli/commands/ops.js +97 -9
  22. package/dist/cli/commands/preview.d.ts +58 -0
  23. package/dist/cli/commands/preview.js +181 -0
  24. package/dist/cli/commands/run.d.ts +1 -17
  25. package/dist/cli/commands/run.js +17 -206
  26. package/dist/cli/commands/setup.js +175 -32
  27. package/dist/cli/index.js +5 -0
  28. package/dist/core/agent-host.d.ts +3 -0
  29. package/dist/core/agent-host.js +1 -1
  30. package/dist/core/api-client.d.ts +10 -2
  31. package/dist/core/api-client.js +13 -8
  32. package/dist/core/capabilities/agent-playbook.js +1 -0
  33. package/dist/core/capabilities/apps.d.ts +9 -9
  34. package/dist/core/capabilities/asset-import.d.ts +4 -1
  35. package/dist/core/capabilities/asset-import.js +15 -6
  36. package/dist/core/capabilities/capture-gameplay.js +1 -1
  37. package/dist/core/capabilities/connect-signal.d.ts +2 -2
  38. package/dist/core/capabilities/content-make/index.d.ts +81 -0
  39. package/dist/core/capabilities/content-make/index.js +706 -0
  40. package/dist/core/capabilities/content-make/media.d.ts +57 -0
  41. package/dist/core/capabilities/content-make/media.js +172 -0
  42. package/dist/core/capabilities/content-make/plan.d.ts +130 -0
  43. package/dist/core/capabilities/content-make/plan.js +433 -0
  44. package/dist/core/capabilities/content-make/render.d.ts +64 -0
  45. package/dist/core/capabilities/content-make/render.js +370 -0
  46. package/dist/core/capabilities/content-make/study.d.ts +13 -0
  47. package/dist/core/capabilities/content-make/study.js +165 -0
  48. package/dist/core/capabilities/content-make/types.d.ts +173 -0
  49. package/dist/core/capabilities/content-make/types.js +6 -0
  50. package/dist/core/capabilities/content-make/voice.d.ts +7 -0
  51. package/dist/core/capabilities/content-make/voice.js +27 -0
  52. package/dist/core/capabilities/events.d.ts +2 -2
  53. package/dist/core/capabilities/export-game.d.ts +4 -1
  54. package/dist/core/capabilities/export-game.js +4 -3
  55. package/dist/core/capabilities/export-presets.d.ts +2 -1
  56. package/dist/core/capabilities/export-presets.js +2 -1
  57. package/dist/core/capabilities/go-live.d.ts +14 -0
  58. package/dist/core/capabilities/go-live.js +408 -0
  59. package/dist/core/capabilities/image-generation.d.ts +6 -6
  60. package/dist/core/capabilities/item-manifest.d.ts +92 -0
  61. package/dist/core/capabilities/item-manifest.js +247 -0
  62. package/dist/core/capabilities/placement.d.ts +7 -7
  63. package/dist/core/capabilities/projects-local.d.ts +68 -0
  64. package/dist/core/capabilities/projects-local.js +172 -0
  65. package/dist/core/capabilities/publish-build.d.ts +13 -2
  66. package/dist/core/capabilities/publish-build.js +87 -21
  67. package/dist/core/capabilities/publish-download.js +1 -1
  68. package/dist/core/capabilities/runtime-control.d.ts +7 -2
  69. package/dist/core/capabilities/runtime-control.js +27 -1
  70. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  71. package/dist/core/capabilities/store-checks.d.ts +45 -0
  72. package/dist/core/capabilities/store-checks.js +85 -0
  73. package/dist/core/capabilities/store-details.d.ts +37 -0
  74. package/dist/core/capabilities/store-details.js +94 -0
  75. package/dist/core/capabilities/summer-bundle.d.ts +21 -2
  76. package/dist/core/capabilities/summer-bundle.js +54 -7
  77. package/dist/core/capabilities/sync-items.d.ts +89 -0
  78. package/dist/core/capabilities/sync-items.js +352 -0
  79. package/dist/core/capabilities/tool-dispatch.js +66 -6
  80. package/dist/core/capabilities/video-ogv.d.ts +49 -0
  81. package/dist/core/capabilities/video-ogv.js +260 -0
  82. package/dist/core/capability-skew.js +2 -2
  83. package/dist/core/cli-usage-beacon.js +26 -2
  84. package/dist/core/cli-usage.d.ts +5 -2
  85. package/dist/core/cli-usage.js +8 -2
  86. package/dist/core/engine-install.d.ts +53 -0
  87. package/dist/core/engine-install.js +93 -0
  88. package/dist/core/engine-installer.d.ts +134 -0
  89. package/dist/core/engine-installer.js +760 -0
  90. package/dist/core/engine-launch.d.ts +126 -0
  91. package/dist/core/engine-launch.js +398 -0
  92. package/dist/core/engine-self-start.d.ts +63 -0
  93. package/dist/core/engine-self-start.js +282 -0
  94. package/dist/core/engine.d.ts +7 -1
  95. package/dist/core/engine.js +9 -2
  96. package/dist/core/error-codes.d.ts +4 -1
  97. package/dist/core/error-codes.js +7 -3
  98. package/dist/core/library-read.d.ts +2 -2
  99. package/dist/core/library-search.d.ts +2 -2
  100. package/dist/core/local-project.d.ts +50 -0
  101. package/dist/core/local-project.js +234 -0
  102. package/dist/core/ops/areas.js +9 -3
  103. package/dist/core/ops/catalog.d.ts +9 -0
  104. package/dist/core/ops/catalog.js +56 -4
  105. package/dist/core/ops/engine-answer.d.ts +4 -1
  106. package/dist/core/ops/engine-answer.js +16 -3
  107. package/dist/core/ops/engine.js +4 -4
  108. package/dist/core/ops/save-files.d.ts +6 -1
  109. package/dist/core/ops/save-files.js +67 -50
  110. package/dist/core/ops/search.js +9 -2
  111. package/dist/core/preview/input.d.ts +35 -0
  112. package/dist/core/preview/input.js +105 -0
  113. package/dist/core/preview/launch-config.d.ts +52 -0
  114. package/dist/core/preview/launch-config.js +137 -0
  115. package/dist/core/preview/page.d.ts +22 -0
  116. package/dist/core/preview/page.js +232 -0
  117. package/dist/core/preview/preview-source.d.ts +179 -0
  118. package/dist/core/preview/preview-source.js +350 -0
  119. package/dist/core/preview/server.d.ts +26 -0
  120. package/dist/core/preview/server.js +218 -0
  121. package/dist/core/telemetry.d.ts +20 -16
  122. package/dist/core/telemetry.js +92 -16
  123. package/dist/core/templates.d.ts +20 -0
  124. package/dist/core/templates.js +300 -3
  125. package/dist/core/tool-profiles.d.ts +2 -2
  126. package/dist/core/tool-profiles.js +1 -0
  127. package/dist/core/usage-fields.d.ts +41 -0
  128. package/dist/core/usage-fields.js +150 -0
  129. package/dist/core/util/sleep.d.ts +3 -0
  130. package/dist/core/util/sleep.js +12 -0
  131. package/dist/installer/agent-config.d.ts +29 -0
  132. package/dist/installer/agent-config.js +75 -11
  133. package/dist/installer/agent-detect.d.ts +44 -0
  134. package/dist/installer/agent-detect.js +166 -0
  135. package/dist/installer/agent-table.d.ts +19 -0
  136. package/dist/installer/agent-table.js +130 -0
  137. package/dist/installer/setup-error.d.ts +19 -0
  138. package/dist/installer/setup-error.js +34 -0
  139. package/dist/installer/version-check.d.ts +5 -0
  140. package/dist/installer/version-check.js +21 -0
  141. package/dist/mcp/ops-surface.d.ts +7 -0
  142. package/dist/mcp/ops-surface.js +40 -3
  143. package/dist/mcp/server.d.ts +14 -4
  144. package/dist/mcp/server.js +42 -8
  145. package/dist/mcp/tool-surface.d.ts +8 -0
  146. package/dist/mcp/tool-surface.js +19 -2
  147. package/dist/mcp/tools/creator-tools.js +92 -14
  148. package/dist/mcp/tools/debug-tools.js +2 -0
  149. package/dist/mcp/tools/local-project-tools.d.ts +6 -0
  150. package/dist/mcp/tools/local-project-tools.js +33 -0
  151. package/dist/mcp/tools/navigation-tools.js +2 -1
  152. package/dist/mcp/tools/project-tools.js +5 -8
  153. package/dist/mcp/tools/seeing-tools.js +1 -1
  154. package/dist/mcp/tools/visual-tools.js +1 -1
  155. package/dist/mcp/tools/with-engine.js +1 -1
  156. package/dist/project-memory/agent-instructions.js +11 -3
  157. package/gemini-extension.json +3 -1
  158. package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
  159. package/library/skills/README.md +12 -8
  160. package/library/skills/animated-loop/SKILL.md +10 -16
  161. package/library/skills/cinematic-cutscene/SKILL.md +7 -18
  162. package/library/skills/headless-scripting/SKILL.md +7 -4
  163. package/library/skills/in-game-purchases/SKILL.md +25 -8
  164. package/library/skills/in-game-purchases/resource.yaml +2 -1
  165. package/library/skills/launch-ready/SKILL.md +351 -0
  166. package/library/skills/launch-ready/resource.yaml +45 -0
  167. package/library/skills/make-content/SKILL.md +202 -0
  168. package/library/skills/make-content/resource.yaml +32 -0
  169. package/library/skills/make-game/SKILL.md +6 -4
  170. package/library/skills/make-game/resource.yaml +2 -1
  171. package/library/skills/navigate-summer/SKILL.md +4 -2
  172. package/library/skills/new-project/SKILL.md +15 -22
  173. package/library/skills/new-project/resource.yaml +2 -1
  174. package/library/skills/painted-25d-world/SKILL.md +366 -0
  175. package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
  176. package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
  177. package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
  178. package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
  179. package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
  180. package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
  181. package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
  182. package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
  183. package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
  184. package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
  185. package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
  186. package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
  187. package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
  188. package/library/skills/painted-25d-world/resource.yaml +39 -0
  189. package/library/skills/publish-your-game/SKILL.md +13 -14
  190. package/library/skills/publish-your-game/resource.yaml +4 -3
  191. package/library/skills/release-checklist/SKILL.md +188 -0
  192. package/library/skills/release-checklist/resource.yaml +40 -0
  193. package/library/skills/release-readiness/SKILL.md +1 -1
  194. package/library/skills/release-readiness/resource.yaml +1 -1
  195. package/library/skills/store-art/SKILL.md +1 -1
  196. package/library/skills/store-art/resource.yaml +1 -1
  197. package/library/skills/store-listing/SKILL.md +1 -1
  198. package/library/skills/store-listing/resource.yaml +1 -1
  199. package/library/skills/summer-cli/SKILL.md +2 -0
  200. package/library/skills/summer-store/SKILL.md +295 -39
  201. package/library/skills/summer-store/resource.yaml +9 -3
  202. package/library/skills/trailer-shot/SKILL.md +3 -3
  203. package/library/templates/README.md +5 -0
  204. package/library/tools/content-make/resource.yaml +132 -0
  205. package/library/tools/frame-shot/GUIDE.md +1 -1
  206. package/library/tools/go-live/resource.yaml +67 -0
  207. package/library/tools/import-from-url/GUIDE.md +1 -0
  208. package/library/tools/import-from-url-batch/GUIDE.md +1 -1
  209. package/library/tools/play/GUIDE.md +2 -0
  210. package/library/tools/projects-create-local/resource.yaml +55 -0
  211. package/library/tools/projects-open-local/resource.yaml +51 -0
  212. package/library/tools/publish-build/resource.yaml +3 -3
  213. package/library/tools/screenshot/GUIDE.md +1 -1
  214. package/library/tools/store-set-details/resource.yaml +114 -0
  215. package/library/tools/sync-items/resource.yaml +72 -0
  216. package/package.json +1 -1
  217. package/registry/generated/aliases.json +6 -0
  218. package/registry/generated/counts.json +3 -3
  219. package/registry/generated/gemini-extension.json +3 -1
  220. package/registry/generated/index.json +463 -37
  221. package/registry/generated/marketplace.claude.json +1 -1
  222. package/registry/generated/plugin.claude-directory.json +4 -2
  223. package/registry/generated/plugin.claude.json +3 -1
  224. package/registry/generated/plugin.codex.json +5 -3
  225. package/registry/generated/plugin.cursor.json +4 -2
  226. package/registry/generated/plugin.factory.json +4 -2
  227. package/registry/generated/server.json +2 -2
  228. package/registry/generated/skills-index.md +12 -8
  229. package/registry/generated/skills-registry.json +62 -5
  230. package/registry/operations/legacy-names.json +40 -0
  231. package/registry/operations/public-catalog.json +10144 -387
@@ -9,6 +9,7 @@ import { appendStoreJsonLine } from "../store.js";
9
9
  import { readJsonResponse } from "../util/http.js";
10
10
  import { readLastExport } from "./export-game.js";
11
11
  import { publishDownload } from "./publish-download.js";
12
+ import { CHECK_FIXES, summarizeChecks } from "./store-checks.js";
12
13
  import { BuildToolError, declarationMismatch, readSummerBundle } from "./summer-bundle.js";
13
14
  /**
14
15
  * summer_publish_build: upload a summer.games export to the creator's game
@@ -51,7 +52,7 @@ const INTAKE_FAILURES = {
51
52
  template_set_unavailable: "Summer Games cannot run this Summer version yet. If you exported with the newest Summer Engine, Summer Games may still be turning that version on: retry the same upload later, there is nothing to update. With an older engine, update Summer Engine and export again.",
52
53
  client_version_conflict: "This version number already belongs to a different export. Use a new clientVersion.",
53
54
  artifact_exists: "This exact export is already uploaded for this game. Export a new version and upload that.",
54
- approval_denied: "Summer Games did not approve this game, so it cannot take new builds.",
55
+ approval_denied: "Summer Games declined this game, so it cannot take new builds.",
55
56
  capacity_limit_reached: "Summer Games is busy right now. Try again in a few minutes.",
56
57
  };
57
58
  export const defaultDependencies = {
@@ -60,14 +61,14 @@ export const defaultDependencies = {
60
61
  now: Date.now,
61
62
  token: () => getStoreAccessToken(),
62
63
  };
63
- export function storeError(status, code, message, requestId) {
64
+ export function storeError(status, code, message, requestId, detail) {
64
65
  if (status === 401) {
65
66
  // The token was sent and refused: signing in again gives the same token
66
67
  // shape and the same answer, so it is not the first thing to try.
67
68
  return new BuildToolError("store_auth_refused", `The Summer store did not accept this sign-in${message ? `: ${message.replace(/\.$/, "")}` : ""}.`, 'Recovery: run "summer doctor" (Store line) to see the store\'s answer. Signing in again does not change a refusal of a valid sign-in. Until the store accepts agent sign-in for this account, upload the .zip in Studio (Store > your game > Exports).', status, requestId ? { requestId } : undefined);
68
69
  }
69
70
  if (status === 403 && code === "owner_approval_required") {
70
- return new BuildToolError("owner_approval_required", message || "Only the game's owner can do this.", "Recovery: nothing went live. Submit with summer_store_submit and give the owner the approval link it returns; a live game's new download is finished by the owner in Studio.", status);
71
+ return new BuildToolError("owner_approval_required", message || "Only the game's owner can do this.", "Recovery: nothing went live. The person's Agent permissions do not let this agent publish: ask them to turn on Publish (Studio > Agent permissions), or send them the approval link store.publish returns. A live game's new web build or download is finished by the owner in Studio.", status);
71
72
  }
72
73
  if (status === 403) {
73
74
  return new BuildToolError("store_forbidden", message || "This account cannot change that game.", "Recovery: check that the game belongs to the signed-in account.", status);
@@ -78,7 +79,7 @@ export function storeError(status, code, message, requestId) {
78
79
  if (status === 503 || code === "store_unavailable") {
79
80
  return new BuildToolError("store_unavailable", message || "Summer Games is not reachable right now.", "Recovery: try again in a few minutes.", status);
80
81
  }
81
- return new BuildToolError(code, INTAKE_FAILURES[code] ?? message ?? `The Summer store refused the request (${status}).`, "Recovery: read the message, fix the export or version, and retry.", status);
82
+ return new BuildToolError(code, INTAKE_FAILURES[code] ?? message ?? `The Summer store refused the request (${status}).`, CHECK_FIXES[code] ? `Recovery: ${CHECK_FIXES[code].fix}` : "Recovery: read the message, fix the export or version, and retry.", status, { ...(requestId ? { requestId } : {}), ...detail });
82
83
  }
83
84
  export function createStore(baseUrl, token, deps) {
84
85
  return {
@@ -93,6 +94,7 @@ export function createStore(baseUrl, token, deps) {
93
94
  authorization: `Bearer ${token}`,
94
95
  ...(options.body === undefined ? {} : { "content-type": "application/json" }),
95
96
  ...(options.key ? { "idempotency-key": options.key } : {}),
97
+ ...options.headers,
96
98
  },
97
99
  body: options.body === undefined ? undefined : JSON.stringify(options.body),
98
100
  redirect: "error",
@@ -107,7 +109,7 @@ export function createStore(baseUrl, token, deps) {
107
109
  if (!response.ok) {
108
110
  const error = body.error && typeof body.error === "object" ? body.error : {};
109
111
  const requestId = [error.requestId, body.requestId, response.headers.get("x-request-id")].find((value) => typeof value === "string" && value);
110
- throw storeError(response.status, typeof error.code === "string" ? error.code : `http_${response.status}`, typeof error.message === "string" ? error.message : undefined, requestId);
112
+ throw storeError(response.status, typeof error.code === "string" ? error.code : `http_${response.status}`, typeof error.message === "string" ? error.message : undefined, requestId, Array.isArray(error.missing) ? { missing: error.missing } : undefined);
111
113
  }
112
114
  return body;
113
115
  },
@@ -312,18 +314,18 @@ async function listGames(deps) {
312
314
  }
313
315
  }
314
316
  /** gameId is required: the error carries the person's games, or why the store did not list them. */
315
- async function listGamesOrThrow(deps) {
317
+ export async function listGamesOrThrow(deps) {
316
318
  const listed = await listGames(deps);
317
319
  if ("games" in listed) {
318
320
  throw new BuildToolError("game_required", listed.games.length
319
321
  ? "gameId is required: pick one of your store games below (the export is uploaded to one existing game)."
320
322
  : "gameId is required, and this account has no store game yet.", listed.games.length
321
323
  ? "Recovery: call again with gameId from the list."
322
- : "Recovery: create the store game first (summer_store_create_game), then call again with its gameId.", undefined, { games: listed.games });
324
+ : "Recovery: create the store game first (games.create), then call again with its gameId.", undefined, { games: listed.games });
323
325
  }
324
326
  throw new BuildToolError("game_required", `gameId is required, and the store did not list your games: ${listed.error}`, "Recovery: copy the game id from Studio (the store page URL) and retry.", undefined, { listError: listed.error });
325
327
  }
326
- async function storeToken(deps) {
328
+ export async function storeToken(deps) {
327
329
  try {
328
330
  return await deps.token();
329
331
  }
@@ -350,7 +352,7 @@ export async function checkStoreGame(gameId, exportTargets, deps) {
350
352
  ? `There is no store game "${gameId}" for this account, so nothing can be uploaded to it.`
351
353
  : `The store did not find game "${gameId}", and did not list your games either: ${listed.error}`, "games" in listed && listed.games.length
352
354
  ? "Recovery: call again with gameId from the list."
353
- : "Recovery: create the store game first (summer_store_create_game), or copy its id from Studio (the store page URL).", 404, "games" in listed ? { games: listed.games } : undefined);
355
+ : "Recovery: create the store game first (games.create), or copy its id from Studio (the store page URL).", 404, "games" in listed ? { games: listed.games } : undefined);
354
356
  }
355
357
  throw error;
356
358
  }
@@ -359,10 +361,57 @@ export async function checkStoreGame(gameId, exportTargets, deps) {
359
361
  const catalog = new Set(targets.map((target) => target.replace(/-(universal|x64)$/, "")));
360
362
  const unlisted = exportTargets.filter((target) => !catalog.has(target));
361
363
  const warnings = unlisted.length
362
- ? [`The store page of "${game.name ?? gameId}" does not list ${unlisted.join(", ")} (it lists ${targets.join(", ") || "no platforms"}). Players on ${unlisted.join(", ")} will not see this build until the store page lists them: add them with summer_store_update_listing.`]
364
+ ? [`The store page of "${game.name ?? gameId}" does not list ${unlisted.join(", ")} (it lists ${targets.join(", ") || "no platforms"}). Players on ${unlisted.join(", ")} will not see this build until the store page lists them: add them with store.update_listing.`]
363
365
  : [];
364
366
  return { game: { gameId, name: typeof game.name === "string" ? game.name : null, status: typeof game.status === "string" ? game.status : null, targets }, warnings };
365
367
  }
368
+ /**
369
+ * Seal the upload. While Summer's malware scan of the stored bytes runs, the
370
+ * store answers 409 malware_scan_pending; that is a wait, not a failure.
371
+ */
372
+ async function sealWhenScanned(store, publicationPath, key, deps, deadline) {
373
+ for (;;) {
374
+ try {
375
+ await store.call("POST", `${publicationPath}:source-complete`, { body: {}, key });
376
+ return;
377
+ }
378
+ catch (error) {
379
+ if (!(error instanceof BuildToolError && error.code === "malware_scan_pending") || deps.now() >= deadline)
380
+ throw error;
381
+ await deps.sleep(POLL_MS);
382
+ }
383
+ }
384
+ }
385
+ /**
386
+ * Why the platform did not take a Build to players when asked: the Game's
387
+ * approval state as the platform reports it. Staff review is becoming a
388
+ * step after launch, so a pending review is reported as Summer's hold, not
389
+ * as something the creator waits on or fixes.
390
+ */
391
+ export async function platformHold(store, gameId) {
392
+ let approval = {};
393
+ try {
394
+ approval = await store.call("GET", `games/${seg(gameId)}/approval`);
395
+ }
396
+ catch {
397
+ // The hold is still reported without the approval state.
398
+ }
399
+ const state = typeof approval.state === "string" ? approval.state : "unknown";
400
+ const review = approval.latestReview && typeof approval.latestReview === "object" ? approval.latestReview : null;
401
+ if (state === "REJECTED" || state === "REVOKED" || review?.decision === "CHANGES_REQUESTED") {
402
+ return {
403
+ by: "summer_decision",
404
+ approvalState: state,
405
+ ...(review ? { decision: review.decision, decidedBy: review.decidedBy ?? null, feedback: review.feedback || null } : {}),
406
+ message: "Summer decided against this game going live as it is. Apply the feedback (store.status shows it in full), then publish again.",
407
+ };
408
+ }
409
+ return {
410
+ by: "summer",
411
+ approvalState: state,
412
+ message: "Every automated check is done; the platform has not released this game's builds yet. That is on Summer's side and needs nothing from you. The Build is kept, and summer_go_live takes it live and reports the moment players get it.",
413
+ };
414
+ }
366
415
  export async function publishBuild(input, overrides = {}) {
367
416
  const deps = { ...defaultDependencies, ...overrides };
368
417
  const gameId = input.gameId?.trim();
@@ -450,7 +499,7 @@ export async function publishBuild(input, overrides = {}) {
450
499
  }
451
500
  const partHashes = plan.partSizeBytes === PART_BYTES ? digest.parts : (await hashParts(file, plan.partSizeBytes)).parts;
452
501
  await uploadParts(async (numbers) => readGrants(await store.call("POST", `${publicationPath}:signParts`, { body: { parts: numbers.map((n) => ({ partNumber: n, sha256: partHashes[n - 1] })) } })), file, digest.sizeBytes, plan, deps);
453
- await store.call("POST", `${publicationPath}:source-complete`, { body: {}, key: key(`seal:${publicationId}`) });
502
+ await sealWhenScanned(store, publicationPath, key(`seal:${publicationId}`), deps, deps.now() + (input.waitSeconds ?? DEFAULT_WAIT_SECONDS) * 1000);
454
503
  uploaded = true;
455
504
  }
456
505
  const deadline = deps.now() + (input.waitSeconds ?? DEFAULT_WAIT_SECONDS) * 1000;
@@ -470,7 +519,9 @@ export async function publishBuild(input, overrides = {}) {
470
519
  ? "Recovery: fix what the message names, export again and retry; the same clientVersion still works, because a refused upload records no version."
471
520
  : code === "template_set_unavailable"
472
521
  ? `Recovery: ${INTAKE_FAILURES.template_set_unavailable.replace(/^Summer Games cannot run this Summer version yet\. /, "")} The same clientVersion works on the retry.`
473
- : "Recovery: export again, use a new clientVersion, and retry.", undefined, { publicationId });
522
+ : CHECK_FIXES[code]
523
+ ? `Recovery: ${CHECK_FIXES[code].fix}`
524
+ : "Recovery: export again, use a new clientVersion, and retry.", undefined, { publicationId, failedCheck: { code, owner: CHECK_FIXES[code]?.owner ?? "creator" } });
474
525
  }
475
526
  if (deps.now() >= deadline) {
476
527
  const result = {
@@ -503,9 +554,17 @@ export async function publishBuild(input, overrides = {}) {
503
554
  });
504
555
  clientPackageId = named.package?.id ?? null;
505
556
  }
557
+ // Summer's automated checks on the client pack, one per platform: wait for
558
+ // them within the same budget, so the answer says whether the build passed.
559
+ let checks = summarizeChecks(publication);
560
+ while (checks.state === "checking" && deps.now() < deadline) {
561
+ await deps.sleep(POLL_MS);
562
+ publication = await store.call("GET", publicationPath);
563
+ checks = summarizeChecks(publication);
564
+ }
506
565
  let published = false;
507
- let publishNote = null;
508
- if (publish) {
566
+ let hold = null;
567
+ if (publish && checks.state !== "failed") {
509
568
  try {
510
569
  await store.call("POST", `${publicationPath}:publish`, { body: {}, key: key(`publish:${publicationId}`) });
511
570
  published = true;
@@ -513,13 +572,14 @@ export async function publishBuild(input, overrides = {}) {
513
572
  catch (error) {
514
573
  if (!(error instanceof BuildToolError && error.status === 409 && error.code === NOT_ACTIONABLE))
515
574
  throw error;
516
- publishNote = "The game has not passed review yet, so this Build waits as a preview.";
575
+ hold = await platformHold(store, gameId);
517
576
  }
518
577
  }
519
- await appendStoreJsonLine("creator-audit.jsonl", { ...audit, outcome: "succeeded", publicationId, buildId, published });
578
+ await appendStoreJsonLine("creator-audit.jsonl", { ...audit, outcome: "succeeded", publicationId, buildId, published, checks: checks.state });
579
+ const failed = checks.checks.filter((check) => check.state === "failed");
520
580
  return {
521
581
  ok: true,
522
- status: published ? "published" : "uploaded",
582
+ status: checks.state === "failed" ? "checks_failed" : published ? "published" : "uploaded",
523
583
  gameId,
524
584
  publicationId,
525
585
  buildId,
@@ -528,11 +588,17 @@ export async function publishBuild(input, overrides = {}) {
528
588
  clientVersion,
529
589
  sha256: digest.sha256,
530
590
  sizeBytes: digest.sizeBytes,
591
+ // passed: every platform passed Summer's automated checks; checking: call again to keep waiting.
592
+ checksState: checks.state,
593
+ checks: checks.checks,
594
+ ...(checks.packFailure ? { packFailure: checks.packFailure } : {}),
531
595
  published,
532
- ...(publishNote ? { publishNote } : {}),
533
- next: published
534
- ? "The Build is approved. Summer checks the client pack per platform; Studio shows the result."
535
- : "The Build exists as a preview. Summer checks the client pack per platform; publish it from Studio or call again with publish=true.",
596
+ ...(hold ? { hold } : {}),
597
+ next: checks.state === "failed"
598
+ ? `Summer's automated checks failed${failed.length ? ` on ${failed.map((check) => check.platform).join(", ")}` : ""}. Apply each fix, export again and upload with a new clientVersion.`
599
+ : checks.state === "checking"
600
+ ? "Summer is still checking the client pack. Call summer_publish_build again with the same file and clientVersion to keep waiting; nothing is uploaded twice."
601
+ : `Every automated check passed. Take it live with summer_go_live (gameId ${gameId}, publicationId ${publicationId}).`,
536
602
  };
537
603
  }
538
604
  catch (error) {
@@ -156,7 +156,7 @@ export async function publishDownload(input, deps) {
156
156
  resumed,
157
157
  ...(version.playUrl ? { playUrl: version.playUrl } : {}),
158
158
  next: outcome === "ready"
159
- ? "The file is checked. Players get it once the store page is live; submit the page with summer_store_submit (the owner approves)."
159
+ ? "The file is checked. Players get it once the store page is live; submit the page with store.publish (the owner approves)."
160
160
  : "Summer is still checking the file. Call summer_publish_build again with the same file and clientVersion to keep waiting; nothing is uploaded twice.",
161
161
  };
162
162
  }
@@ -138,7 +138,7 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
138
138
  mode: z.ZodOptional<z.ZodEnum<["queue_free", "free"]>>;
139
139
  instance: z.ZodOptional<z.ZodString>;
140
140
  }, "strip", z.ZodTypeAny, {
141
- action: "spawn" | "free";
141
+ action: "free" | "spawn";
142
142
  mode?: "free" | "queue_free" | undefined;
143
143
  name?: string | undefined;
144
144
  instance?: string | undefined;
@@ -147,7 +147,7 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
147
147
  parent?: string | undefined;
148
148
  props?: Record<string, string | number | boolean> | undefined;
149
149
  }, {
150
- action: "spawn" | "free";
150
+ action: "free" | "spawn";
151
151
  mode?: "free" | "queue_free" | undefined;
152
152
  name?: string | undefined;
153
153
  instance?: string | undefined;
@@ -552,3 +552,8 @@ export interface PlayGameClient extends CapabilityAdvertisingClient {
552
552
  * `scene` into the op and would drop agent/seed/instance.
553
553
  */
554
554
  export declare function playGame(client: PlayGameClient, args: PlayGameArgs): Promise<unknown>;
555
+ /**
556
+ * How many game clients a play started, for the usage line: the Local Play
557
+ * echo's players + spectators, else 1. Null when the play failed.
558
+ */
559
+ export declare function playClientCount(result: unknown): number | null;
@@ -30,6 +30,7 @@ import { z } from "zod";
30
30
  import { ToolInputError } from "../tool-errors.js";
31
31
  import { missingEngineOpResult } from "../capability-skew.js";
32
32
  import { extractOpError, getFailureReason, withOldEngineHint } from "./engine-receipt.js";
33
+ import { noteToolUsage } from "../usage-fields.js";
33
34
  // ---------------------------------------------------------------------------
34
35
  // Op kinds
35
36
  // ---------------------------------------------------------------------------
@@ -538,7 +539,7 @@ export const inputEventSchema = z.object({
538
539
  pressed: z.boolean().optional().describe("type:'action'|'key' — press (default true) or release."),
539
540
  strength: z.number().optional().describe("type:'action' — press strength 0..1 (default 1). type:'axis' — signed strength; negative selects action_negative."),
540
541
  hold_ms: z.number().int().optional().describe("type:'action'|'key' — auto-release after this many ms (0 = stays pressed until a release event)."),
541
- keycode: z.number().int().optional().describe("type:'key' — Godot Key enum value (e.g. 32 = Space, 4194320 = Right arrow)."),
542
+ keycode: z.number().int().optional().describe("type:'key' — Godot Key enum value (e.g. 32 = Space, 4194321 = Right arrow)."),
542
543
  physical_keycode: z.number().int().optional().describe("type:'key' — physical Key enum value (alternative to keycode)."),
543
544
  position: z
544
545
  .tuple([z.number(), z.number()])
@@ -1058,6 +1059,31 @@ export function withPlayPostureEcho(result, args) {
1058
1059
  * `scene` into the op and would drop agent/seed/instance.
1059
1060
  */
1060
1061
  export async function playGame(client, args) {
1062
+ const result = await playGameOnce(client, args);
1063
+ const instanceCount = playClientCount(result);
1064
+ if (instanceCount !== null)
1065
+ noteToolUsage({ instanceCount });
1066
+ return result;
1067
+ }
1068
+ /**
1069
+ * How many game clients a play started, for the usage line: the Local Play
1070
+ * echo's players + spectators, else 1. Null when the play failed.
1071
+ */
1072
+ export function playClientCount(result) {
1073
+ if (!result || typeof result !== "object" || extractOpError(result))
1074
+ return null;
1075
+ const envelope = result;
1076
+ if (envelope.ok === false)
1077
+ return null;
1078
+ const payload = envelope.results?.[0] ?? envelope;
1079
+ const local = payload.local_play;
1080
+ if (local && typeof local === "object" && local.applied === true) {
1081
+ const count = (value) => (typeof value === "number" && Number.isInteger(value) && value > 0 ? value : 0);
1082
+ return Math.max(1, count(local.players) + count(local.spectators));
1083
+ }
1084
+ return 1;
1085
+ }
1086
+ async function playGameOnce(client, args) {
1061
1087
  if (!playNeedsOp(args))
1062
1088
  return client.play(args.scene);
1063
1089
  // Validate the combination first (nothing sent); then, because an engine
@@ -119,11 +119,11 @@ export declare const frameNodesArgsSchema: z.ZodObject<{
119
119
  }, "strict", z.ZodTypeAny, {
120
120
  nodes: string[];
121
121
  fill?: number | undefined;
122
+ from?: string | undefined;
122
123
  scenePath?: string | undefined;
123
124
  fov?: number | undefined;
124
125
  marks?: boolean | undefined;
125
126
  max_marks?: number | undefined;
126
- from?: string | undefined;
127
127
  direction?: "top" | "front" | "iso" | "back" | "left" | "right" | undefined;
128
128
  bookmark_name?: string | undefined;
129
129
  max_size?: number | undefined;
@@ -131,11 +131,11 @@ export declare const frameNodesArgsSchema: z.ZodObject<{
131
131
  }, {
132
132
  nodes: string[];
133
133
  fill?: number | undefined;
134
+ from?: string | undefined;
134
135
  scenePath?: string | undefined;
135
136
  fov?: number | undefined;
136
137
  marks?: boolean | undefined;
137
138
  max_marks?: number | undefined;
138
- from?: string | undefined;
139
139
  direction?: "top" | "front" | "iso" | "back" | "left" | "right" | undefined;
140
140
  bookmark_name?: string | undefined;
141
141
  max_size?: number | undefined;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Summer's automated checks on an uploaded game, read the way an agent can act
3
+ * on them: one entry per check with its state, the platform's code and a fix.
4
+ *
5
+ * The codes and their meaning come from the Summer platform's management API
6
+ * (api/management/openapi.yaml, build-publications and client packages) and
7
+ * its pipeline docs (docs/build-publications.md, docs/client-pack-publication.md).
8
+ * Two kinds of failure are kept apart, because only one is the creator's to fix:
9
+ * - creator: the export or its content (a fix is named);
10
+ * - summer: a problem on Summer's side (nothing to change; it retries or
11
+ * Summer resolves it).
12
+ */
13
+ export type CheckOwner = "creator" | "summer";
14
+ export interface CheckFix {
15
+ owner: CheckOwner;
16
+ fix: string;
17
+ }
18
+ /** Why an upload or one of its checks failed, and what to do about it. */
19
+ export declare const CHECK_FIXES: Record<string, CheckFix>;
20
+ export interface PlatformCheck {
21
+ platform: string;
22
+ state: "checking" | "passed" | "failed";
23
+ code?: string;
24
+ message?: string;
25
+ owner?: CheckOwner;
26
+ fix?: string;
27
+ }
28
+ /** A failure line with its owner and fix (unknown codes are the creator's, with the platform's message). */
29
+ export declare function describeFailure(code: string | null | undefined, message: string | null | undefined): {
30
+ code: string;
31
+ message?: string;
32
+ owner: CheckOwner;
33
+ fix: string;
34
+ };
35
+ /** Each platform the client pack is checked on, from a build publication's clientPackage. */
36
+ export declare function platformChecks(clientPackage: Record<string, unknown> | null | undefined): PlatformCheck[];
37
+ export interface ChecksSummary {
38
+ /** passed: every platform passed; failed: the pack or a platform failed; checking: not done yet. */
39
+ state: "checking" | "passed" | "failed";
40
+ checks: PlatformCheck[];
41
+ /** The pack itself was refused (state rejected), before any platform ran. */
42
+ packFailure?: ReturnType<typeof describeFailure>;
43
+ }
44
+ /** Where the automated checks of one build publication stand. */
45
+ export declare function summarizeChecks(publication: Record<string, any>): ChecksSummary;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Summer's automated checks on an uploaded game, read the way an agent can act
3
+ * on them: one entry per check with its state, the platform's code and a fix.
4
+ *
5
+ * The codes and their meaning come from the Summer platform's management API
6
+ * (api/management/openapi.yaml, build-publications and client packages) and
7
+ * its pipeline docs (docs/build-publications.md, docs/client-pack-publication.md).
8
+ * Two kinds of failure are kept apart, because only one is the creator's to fix:
9
+ * - creator: the export or its content (a fix is named);
10
+ * - summer: a problem on Summer's side (nothing to change; it retries or
11
+ * Summer resolves it).
12
+ */
13
+ /** Why an upload or one of its checks failed, and what to do about it. */
14
+ export const CHECK_FIXES = {
15
+ // Intake (build-publications errorCode).
16
+ bundle_invalid: { owner: "creator", fix: "Export again with summer_export_game (the summer.games preset) and upload the .zip exactly as Summer Engine wrote it." },
17
+ declaration_mismatch: { owner: "creator", fix: "Export again with summer_export_game alignDeclaration:true, or with targets equal to what summer.build.json declares." },
18
+ client_version_conflict: { owner: "creator", fix: "This version number already names different bytes: upload with a new clientVersion." },
19
+ artifact_exists: { owner: "creator", fix: "These exact bytes are already a build of this game: use that build (store.status lists it) or export a new version." },
20
+ engine_build_unreleased: {
21
+ owner: "creator",
22
+ fix: "The export came from a Summer Engine build Summer never released (a development or nightly build). Install the released Summer Engine, export again and upload.",
23
+ },
24
+ attempts_exhausted: { owner: "creator", fix: "Summer could not read the upload in five tries, usually because it is very large. Make the export smaller (remove unused assets), export again and upload with a new clientVersion." },
25
+ approval_denied: { owner: "creator", fix: "Summer declined this game. Read the decision and its feedback with store.status before uploading again." },
26
+ malware_detected: { owner: "creator", fix: "The malware scan found a threat in the upload. Check the project's files, plugins and add-ons, export again on a clean machine and upload with a new clientVersion." },
27
+ malware_scan_unsupported: { owner: "creator", fix: "The malware scanner could not read this file. Upload the .zip exactly as summer_export_game wrote it (no re-zipping, no password)." },
28
+ materializing_failed: { owner: "creator", fix: "Summer could not unpack the game's server. Read errorMessage, fix the server export, export again and upload with a new clientVersion." },
29
+ building_failed: { owner: "creator", fix: "The game's server image did not build. Read errorMessage, fix it, export again and upload with a new clientVersion." },
30
+ verifying_failed: {
31
+ owner: "creator",
32
+ fix: "The secrets scan found a key or password in the server. Remove it from the project (scripts, .env and config files, the export's include filters), revoke it where it was issued, export again and upload with a new clientVersion.",
33
+ },
34
+ // Client pack and per-platform checks (client packages).
35
+ package_not_verified: {
36
+ owner: "creator",
37
+ fix: "The client pack failed inspection: its main scene must open, and it must carry the export stamp Summer Engine writes. Read errorMessage, fix the project, export again and upload with a new clientVersion.",
38
+ },
39
+ client_platform_unavailable: {
40
+ owner: "creator",
41
+ fix: "Summer cannot run this kind of game on this platform yet (for example web for a game with a server). Export without that platform; ship web as a download (summer_export_game format download).",
42
+ },
43
+ qualification_revoked: { owner: "creator", fix: "Summer withdrew this platform's check result. Export a new version and upload it." },
44
+ // Summer's side: nothing for the creator to change.
45
+ template_set_unavailable: { owner: "summer", fix: "Summer Games is still turning this Summer version on. Nothing to change: retry the same upload later." },
46
+ capacity_limit_reached: { owner: "summer", fix: "Summer Games is busy. Retry the same upload in a few minutes." },
47
+ verification_unavailable: { owner: "summer", fix: "Summer could not run this platform's check right now and retries it. Nothing to change." },
48
+ verification_blocked: { owner: "summer", fix: "Summer held this platform's check for its team. Nothing to change in the game; it continues once Summer resolves it." },
49
+ };
50
+ /** A failure line with its owner and fix (unknown codes are the creator's, with the platform's message). */
51
+ export function describeFailure(code, message) {
52
+ const known = code ? CHECK_FIXES[code] : undefined;
53
+ return {
54
+ code: code || "check_failed",
55
+ ...(message ? { message: message.length > 400 ? `${message.slice(0, 400)}...` : message } : {}),
56
+ owner: known?.owner ?? "creator",
57
+ fix: known?.fix ?? "Read message, fix what it names in the project, export again and upload with a new clientVersion.",
58
+ };
59
+ }
60
+ const text = (value) => (typeof value === "string" && value ? value : undefined);
61
+ /** Each platform the client pack is checked on, from a build publication's clientPackage. */
62
+ export function platformChecks(clientPackage) {
63
+ const verifications = Array.isArray(clientPackage?.verifications) ? clientPackage.verifications : [];
64
+ return verifications.map((check) => {
65
+ const platform = text(check.platform) ?? "unknown";
66
+ if (check.state === "ready" && text(check.qualificationId))
67
+ return { platform, state: "passed" };
68
+ if (check.state === "failed")
69
+ return { platform, state: "failed", ...describeFailure(text(check.errorCode), text(check.errorMessage)) };
70
+ return { platform, state: "checking" };
71
+ });
72
+ }
73
+ /** Where the automated checks of one build publication stand. */
74
+ export function summarizeChecks(publication) {
75
+ const pack = publication.clientPackage && typeof publication.clientPackage === "object" ? publication.clientPackage : null;
76
+ if (pack?.state === "rejected") {
77
+ return { state: "failed", checks: platformChecks(pack), packFailure: describeFailure("package_not_verified", text(pack.verificationError)) };
78
+ }
79
+ const checks = platformChecks(pack);
80
+ // Wait for every platform: a failure on one does not stop the others from passing.
81
+ if (!pack || pack.state !== "uploaded" || checks.length === 0 || checks.some((check) => check.state === "checking")) {
82
+ return { state: "checking", checks };
83
+ }
84
+ return { state: checks.some((check) => check.state === "failed") ? "failed" : "passed", checks };
85
+ }
@@ -0,0 +1,37 @@
1
+ import { type PublishBuildDependencies } from "./publish-build.js";
2
+ /**
3
+ * summer_store_set_details: the store page sections the hosted
4
+ * store.update_listing does not edit, on the store page draft:
5
+ * genres, features, credits, language support, system requirements, the
6
+ * content questionnaire (contentSurvey) and the AI disclosure. Same route as
7
+ * Studio's store page editor:
8
+ *
9
+ * GET games/{g}/revisions the newest revision
10
+ * POST games/{g}/revisions a new draft from it, when it is not a draft
11
+ * PATCH games/{g}/revisions/{r} the sections, with the revision's ETag
12
+ *
13
+ * Each section given replaces that section; null clears it; an omitted one
14
+ * keeps its value (summer-platform api/management/openapi.yaml,
15
+ * PatchRevision and the store page section schemas). Saving never publishes.
16
+ */
17
+ export declare const STORE_FEATURES: readonly ["single_player", "online_multiplayer", "local_multiplayer", "online_coop", "local_coop", "cross_platform", "controller_full", "controller_partial", "cloud_saves", "plays_in_browser", "level_editor", "achievements", "in_game_purchases"];
18
+ export declare const AI_USES: readonly ["assets", "code", "generatedGameplay"];
19
+ export declare const SURVEY_LEVELS: {
20
+ readonly violence: readonly ["none", "fantasy", "realistic"];
21
+ readonly bloodGore: readonly ["none", "mild", "intense"];
22
+ readonly sexualContent: readonly ["none", "suggestive", "explicit"];
23
+ readonly nudity: readonly ["none", "partial", "full"];
24
+ readonly language: readonly ["none", "mild", "strong"];
25
+ readonly substances: readonly ["none", "referenced", "used"];
26
+ readonly gambling: readonly ["none", "simulated", "realMoney"];
27
+ };
28
+ export declare const DETAIL_SECTIONS: readonly ["genres", "features", "credits", "languageSupport", "systemRequirements", "contentSurvey", "aiDisclosure"];
29
+ type Section = (typeof DETAIL_SECTIONS)[number];
30
+ export type StoreDetailsInput = {
31
+ gameId?: string;
32
+ face: "cli" | "mcp";
33
+ } & Partial<Record<Section, unknown>>;
34
+ export declare function setStoreDetails(input: StoreDetailsInput, overrides?: Partial<PublishBuildDependencies>): Promise<Record<string, unknown> & {
35
+ ok: true;
36
+ }>;
37
+ export {};
@@ -0,0 +1,94 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolveGatewayUrl } from "../config.js";
3
+ import { createStore, defaultDependencies, listGamesOrThrow, storeToken } from "./publish-build.js";
4
+ import { BuildToolError } from "./summer-bundle.js";
5
+ /**
6
+ * summer_store_set_details: the store page sections the hosted
7
+ * store.update_listing does not edit, on the store page draft:
8
+ * genres, features, credits, language support, system requirements, the
9
+ * content questionnaire (contentSurvey) and the AI disclosure. Same route as
10
+ * Studio's store page editor:
11
+ *
12
+ * GET games/{g}/revisions the newest revision
13
+ * POST games/{g}/revisions a new draft from it, when it is not a draft
14
+ * PATCH games/{g}/revisions/{r} the sections, with the revision's ETag
15
+ *
16
+ * Each section given replaces that section; null clears it; an omitted one
17
+ * keeps its value (summer-platform api/management/openapi.yaml,
18
+ * PatchRevision and the store page section schemas). Saving never publishes.
19
+ */
20
+ export const STORE_FEATURES = [
21
+ "single_player",
22
+ "online_multiplayer",
23
+ "local_multiplayer",
24
+ "online_coop",
25
+ "local_coop",
26
+ "cross_platform",
27
+ "controller_full",
28
+ "controller_partial",
29
+ "cloud_saves",
30
+ "plays_in_browser",
31
+ "level_editor",
32
+ "achievements",
33
+ "in_game_purchases",
34
+ ];
35
+ export const AI_USES = ["assets", "code", "generatedGameplay"];
36
+ export const SURVEY_LEVELS = {
37
+ violence: ["none", "fantasy", "realistic"],
38
+ bloodGore: ["none", "mild", "intense"],
39
+ sexualContent: ["none", "suggestive", "explicit"],
40
+ nudity: ["none", "partial", "full"],
41
+ language: ["none", "mild", "strong"],
42
+ substances: ["none", "referenced", "used"],
43
+ gambling: ["none", "simulated", "realMoney"],
44
+ };
45
+ export const DETAIL_SECTIONS = ["genres", "features", "credits", "languageSupport", "systemRequirements", "contentSurvey", "aiDisclosure"];
46
+ const IN_REVIEW = ["SUBMITTED", "VERIFYING", "SCHEDULED"];
47
+ const seg = encodeURIComponent;
48
+ /** The store page refusals this tool can name before anything is sent. */
49
+ function checkSections(input) {
50
+ const ai = input.aiDisclosure;
51
+ if (ai && Array.isArray(ai.uses) && ai.uses.includes("generatedGameplay") && !ai.description?.trim()) {
52
+ throw new BuildToolError("ai_disclosure_incomplete", "aiDisclosure uses generatedGameplay, which needs a description of what is generated while people play and its guardrails.", "Recovery: add aiDisclosure.description, then call again.");
53
+ }
54
+ }
55
+ async function editableDraft(store, gameId, key) {
56
+ const revisions = await store.call("GET", `games/${seg(gameId)}/revisions?limit=20`);
57
+ const newest = (revisions.items ?? []).reduce((best, item) => (!best || Number(item.sequence ?? 0) > Number(best.sequence ?? 0) ? item : best), null);
58
+ if (!newest)
59
+ throw new BuildToolError("no_store_page", "This game has no store page yet.", "Recovery: create it with games.create, then call again.");
60
+ if (newest.state === "DRAFT")
61
+ return newest;
62
+ if (IN_REVIEW.includes(newest.state)) {
63
+ throw new BuildToolError("page_publishing", "The store page is being published right now, so it cannot change until that finishes.", "Recovery: read summer_go_live (or store.status) for where it stands; once it is live, call this again to edit a new draft.");
64
+ }
65
+ return store.call("POST", `games/${seg(gameId)}/revisions`, { body: {}, key });
66
+ }
67
+ export async function setStoreDetails(input, overrides = {}) {
68
+ const deps = { ...defaultDependencies, ...overrides };
69
+ const gameId = input.gameId?.trim();
70
+ if (!gameId)
71
+ return listGamesOrThrow(deps);
72
+ const patch = Object.fromEntries(DETAIL_SECTIONS.filter((section) => input[section] !== undefined).map((section) => [section, input[section]]));
73
+ if (!Object.keys(patch).length) {
74
+ throw new BuildToolError("nothing_to_change", "No store page section was given.", `Recovery: pass at least one of ${DETAIL_SECTIONS.join(", ")}.`);
75
+ }
76
+ checkSections(input);
77
+ const store = createStore(await resolveGatewayUrl(), await storeToken(deps), deps);
78
+ const key = (step) => createHash("sha256").update(`store-details:${gameId}:${step}:${JSON.stringify(patch)}`).digest("hex");
79
+ const draft = await editableDraft(store, gameId, key("fork"));
80
+ const saved = await store.call("PATCH", `games/${seg(gameId)}/revisions/${seg(String(draft.revisionId))}`, {
81
+ body: patch,
82
+ key: key(`patch:${draft.revisionId}:${draft.etagRevision}`),
83
+ headers: { "x-summer-revision": `"game-revision:${draft.revisionId}:${draft.etagRevision}"` },
84
+ });
85
+ return {
86
+ ok: true,
87
+ status: "saved",
88
+ gameId,
89
+ revisionId: saved.revisionId ?? draft.revisionId,
90
+ state: saved.state ?? draft.state,
91
+ saved: Object.fromEntries(Object.keys(patch).map((section) => [section, saved[section] ?? patch[section]])),
92
+ next: "Saved on the store page draft; nothing is published. Read it with store.get_listing; take it live with summer_go_live.",
93
+ };
94
+ }