summer-engine 4.3.0 → 4.5.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 (218) 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 +87 -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/index.js +5 -0
  27. package/dist/core/agent-host.d.ts +3 -0
  28. package/dist/core/agent-host.js +1 -1
  29. package/dist/core/api-client.d.ts +10 -2
  30. package/dist/core/api-client.js +13 -8
  31. package/dist/core/capabilities/agent-playbook.js +1 -0
  32. package/dist/core/capabilities/apps.d.ts +9 -9
  33. package/dist/core/capabilities/asset-import.d.ts +4 -1
  34. package/dist/core/capabilities/asset-import.js +15 -6
  35. package/dist/core/capabilities/capture-gameplay.js +1 -1
  36. package/dist/core/capabilities/connect-signal.d.ts +2 -2
  37. package/dist/core/capabilities/content-make/index.d.ts +81 -0
  38. package/dist/core/capabilities/content-make/index.js +706 -0
  39. package/dist/core/capabilities/content-make/media.d.ts +57 -0
  40. package/dist/core/capabilities/content-make/media.js +172 -0
  41. package/dist/core/capabilities/content-make/plan.d.ts +130 -0
  42. package/dist/core/capabilities/content-make/plan.js +433 -0
  43. package/dist/core/capabilities/content-make/render.d.ts +64 -0
  44. package/dist/core/capabilities/content-make/render.js +370 -0
  45. package/dist/core/capabilities/content-make/study.d.ts +13 -0
  46. package/dist/core/capabilities/content-make/study.js +165 -0
  47. package/dist/core/capabilities/content-make/types.d.ts +173 -0
  48. package/dist/core/capabilities/content-make/types.js +6 -0
  49. package/dist/core/capabilities/content-make/voice.d.ts +7 -0
  50. package/dist/core/capabilities/content-make/voice.js +27 -0
  51. package/dist/core/capabilities/events.d.ts +2 -2
  52. package/dist/core/capabilities/export-game.d.ts +4 -1
  53. package/dist/core/capabilities/export-game.js +4 -3
  54. package/dist/core/capabilities/export-presets.d.ts +2 -1
  55. package/dist/core/capabilities/export-presets.js +2 -1
  56. package/dist/core/capabilities/go-live.d.ts +14 -0
  57. package/dist/core/capabilities/go-live.js +408 -0
  58. package/dist/core/capabilities/image-generation.d.ts +6 -6
  59. package/dist/core/capabilities/item-manifest.d.ts +92 -0
  60. package/dist/core/capabilities/item-manifest.js +247 -0
  61. package/dist/core/capabilities/placement.d.ts +7 -7
  62. package/dist/core/capabilities/projects-local.d.ts +68 -0
  63. package/dist/core/capabilities/projects-local.js +172 -0
  64. package/dist/core/capabilities/publish-build.d.ts +13 -2
  65. package/dist/core/capabilities/publish-build.js +87 -21
  66. package/dist/core/capabilities/publish-download.js +1 -1
  67. package/dist/core/capabilities/runtime-control.d.ts +7 -2
  68. package/dist/core/capabilities/runtime-control.js +27 -1
  69. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  70. package/dist/core/capabilities/store-checks.d.ts +45 -0
  71. package/dist/core/capabilities/store-checks.js +85 -0
  72. package/dist/core/capabilities/store-details.d.ts +37 -0
  73. package/dist/core/capabilities/store-details.js +94 -0
  74. package/dist/core/capabilities/summer-bundle.d.ts +21 -2
  75. package/dist/core/capabilities/summer-bundle.js +54 -7
  76. package/dist/core/capabilities/sync-items.d.ts +89 -0
  77. package/dist/core/capabilities/sync-items.js +352 -0
  78. package/dist/core/capabilities/tool-dispatch.js +66 -6
  79. package/dist/core/capabilities/video-ogv.d.ts +49 -0
  80. package/dist/core/capabilities/video-ogv.js +260 -0
  81. package/dist/core/capability-skew.js +2 -2
  82. package/dist/core/cli-usage-beacon.js +26 -2
  83. package/dist/core/cli-usage.d.ts +5 -2
  84. package/dist/core/cli-usage.js +8 -2
  85. package/dist/core/engine-install.d.ts +53 -0
  86. package/dist/core/engine-install.js +93 -0
  87. package/dist/core/engine-installer.d.ts +134 -0
  88. package/dist/core/engine-installer.js +760 -0
  89. package/dist/core/engine-launch.d.ts +126 -0
  90. package/dist/core/engine-launch.js +398 -0
  91. package/dist/core/engine-self-start.d.ts +63 -0
  92. package/dist/core/engine-self-start.js +282 -0
  93. package/dist/core/engine.d.ts +7 -1
  94. package/dist/core/engine.js +9 -2
  95. package/dist/core/error-codes.d.ts +4 -1
  96. package/dist/core/error-codes.js +7 -3
  97. package/dist/core/library-read.d.ts +2 -2
  98. package/dist/core/library-search.d.ts +2 -2
  99. package/dist/core/local-project.d.ts +50 -0
  100. package/dist/core/local-project.js +234 -0
  101. package/dist/core/ops/areas.js +9 -3
  102. package/dist/core/ops/catalog.d.ts +9 -0
  103. package/dist/core/ops/catalog.js +56 -4
  104. package/dist/core/ops/engine-answer.d.ts +4 -1
  105. package/dist/core/ops/engine-answer.js +16 -3
  106. package/dist/core/ops/engine.js +4 -4
  107. package/dist/core/ops/save-files.d.ts +6 -1
  108. package/dist/core/ops/save-files.js +67 -50
  109. package/dist/core/ops/search.js +9 -2
  110. package/dist/core/preview/input.d.ts +35 -0
  111. package/dist/core/preview/input.js +105 -0
  112. package/dist/core/preview/launch-config.d.ts +52 -0
  113. package/dist/core/preview/launch-config.js +137 -0
  114. package/dist/core/preview/page.d.ts +22 -0
  115. package/dist/core/preview/page.js +232 -0
  116. package/dist/core/preview/preview-source.d.ts +179 -0
  117. package/dist/core/preview/preview-source.js +350 -0
  118. package/dist/core/preview/server.d.ts +26 -0
  119. package/dist/core/preview/server.js +218 -0
  120. package/dist/core/telemetry.d.ts +20 -16
  121. package/dist/core/telemetry.js +92 -16
  122. package/dist/core/templates.d.ts +20 -0
  123. package/dist/core/templates.js +300 -3
  124. package/dist/core/tool-profiles.d.ts +2 -2
  125. package/dist/core/tool-profiles.js +1 -0
  126. package/dist/core/usage-fields.d.ts +41 -0
  127. package/dist/core/usage-fields.js +150 -0
  128. package/dist/core/util/sleep.d.ts +3 -0
  129. package/dist/core/util/sleep.js +12 -0
  130. package/dist/mcp/ops-surface.js +28 -2
  131. package/dist/mcp/server.d.ts +14 -4
  132. package/dist/mcp/server.js +42 -8
  133. package/dist/mcp/tool-surface.d.ts +8 -0
  134. package/dist/mcp/tool-surface.js +19 -2
  135. package/dist/mcp/tools/creator-tools.js +92 -14
  136. package/dist/mcp/tools/debug-tools.js +2 -0
  137. package/dist/mcp/tools/local-project-tools.d.ts +6 -0
  138. package/dist/mcp/tools/local-project-tools.js +33 -0
  139. package/dist/mcp/tools/navigation-tools.js +2 -1
  140. package/dist/mcp/tools/project-tools.js +5 -8
  141. package/dist/mcp/tools/seeing-tools.js +1 -1
  142. package/dist/mcp/tools/visual-tools.js +1 -1
  143. package/dist/mcp/tools/with-engine.js +1 -1
  144. package/gemini-extension.json +3 -1
  145. package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
  146. package/library/skills/README.md +12 -8
  147. package/library/skills/animated-loop/SKILL.md +10 -16
  148. package/library/skills/cinematic-cutscene/SKILL.md +7 -18
  149. package/library/skills/headless-scripting/SKILL.md +7 -4
  150. package/library/skills/in-game-purchases/SKILL.md +25 -8
  151. package/library/skills/in-game-purchases/resource.yaml +2 -1
  152. package/library/skills/launch-ready/SKILL.md +351 -0
  153. package/library/skills/launch-ready/resource.yaml +45 -0
  154. package/library/skills/make-content/SKILL.md +202 -0
  155. package/library/skills/make-content/resource.yaml +32 -0
  156. package/library/skills/make-game/SKILL.md +2 -0
  157. package/library/skills/make-game/resource.yaml +2 -1
  158. package/library/skills/navigate-summer/SKILL.md +4 -2
  159. package/library/skills/new-project/SKILL.md +15 -22
  160. package/library/skills/new-project/resource.yaml +2 -1
  161. package/library/skills/painted-25d-world/SKILL.md +366 -0
  162. package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
  163. package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
  164. package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
  165. package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
  166. package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
  167. package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
  168. package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
  169. package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
  170. package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
  171. package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
  172. package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
  173. package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
  174. package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
  175. package/library/skills/painted-25d-world/resource.yaml +39 -0
  176. package/library/skills/publish-your-game/SKILL.md +13 -14
  177. package/library/skills/publish-your-game/resource.yaml +4 -3
  178. package/library/skills/release-checklist/SKILL.md +188 -0
  179. package/library/skills/release-checklist/resource.yaml +40 -0
  180. package/library/skills/release-readiness/SKILL.md +1 -1
  181. package/library/skills/release-readiness/resource.yaml +1 -1
  182. package/library/skills/store-art/SKILL.md +1 -1
  183. package/library/skills/store-art/resource.yaml +1 -1
  184. package/library/skills/store-listing/SKILL.md +1 -1
  185. package/library/skills/store-listing/resource.yaml +1 -1
  186. package/library/skills/summer-cli/SKILL.md +2 -0
  187. package/library/skills/summer-store/SKILL.md +295 -39
  188. package/library/skills/summer-store/resource.yaml +9 -3
  189. package/library/skills/trailer-shot/SKILL.md +3 -3
  190. package/library/templates/README.md +5 -0
  191. package/library/tools/content-make/resource.yaml +132 -0
  192. package/library/tools/frame-shot/GUIDE.md +1 -1
  193. package/library/tools/go-live/resource.yaml +67 -0
  194. package/library/tools/import-from-url/GUIDE.md +1 -0
  195. package/library/tools/import-from-url-batch/GUIDE.md +1 -1
  196. package/library/tools/play/GUIDE.md +2 -0
  197. package/library/tools/projects-create-local/resource.yaml +55 -0
  198. package/library/tools/projects-open-local/resource.yaml +51 -0
  199. package/library/tools/publish-build/resource.yaml +3 -3
  200. package/library/tools/screenshot/GUIDE.md +1 -1
  201. package/library/tools/store-set-details/resource.yaml +114 -0
  202. package/library/tools/sync-items/resource.yaml +72 -0
  203. package/package.json +1 -1
  204. package/registry/generated/aliases.json +6 -0
  205. package/registry/generated/counts.json +3 -3
  206. package/registry/generated/gemini-extension.json +3 -1
  207. package/registry/generated/index.json +463 -37
  208. package/registry/generated/marketplace.claude.json +1 -1
  209. package/registry/generated/plugin.claude-directory.json +4 -2
  210. package/registry/generated/plugin.claude.json +3 -1
  211. package/registry/generated/plugin.codex.json +5 -3
  212. package/registry/generated/plugin.cursor.json +4 -2
  213. package/registry/generated/plugin.factory.json +4 -2
  214. package/registry/generated/server.json +2 -2
  215. package/registry/generated/skills-index.md +12 -8
  216. package/registry/generated/skills-registry.json +62 -5
  217. package/registry/operations/legacy-names.json +40 -0
  218. package/registry/operations/public-catalog.json +10144 -387
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Writing a new Summer project to disk: the built-in generators, the pinned
3
+ * template fetch, the autopilot scaffold, `.summer/project.json` and the
4
+ * agent instruction files. Shared by `summer create`, `summer dev` and the
5
+ * MCP's projects.create_local.
6
+ */
7
+ import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { dirname, join, resolve } from "path";
10
+ import { checkEngineHealth, getApiPort } from "./engine.js";
11
+ import { PACKAGE_ROOT } from "./package-root.js";
12
+ import { SUMMER_ENGINE_COMPATIBILITY } from "./summer-compatibility.js";
13
+ import { fetchPinnedTemplate } from "./templates.js";
14
+ import { noteToolUsage, templateTags } from "./usage-fields.js";
15
+ import { writeProjectManifest } from "../project-memory/project-manifest.js";
16
+ import { writeAgentInstructions } from "../project-memory/agent-instructions.js";
17
+ import { TOOLKIT_VERSION as toolkitVersion } from "./version.js";
18
+ /** Summer Engine's version when it is reachable right now, else undefined —
19
+ * the manifest then omits engine_version rather than guessing. */
20
+ async function detectEngineVersion() {
21
+ try {
22
+ return (await checkEngineHealth(await getApiPort()))?.version;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ export const BUILTIN_GENERATORS = {
29
+ empty: (dir, name) => {
30
+ writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
31
+ writeFileSync(join(dir, "main.tscn"), emptyScene());
32
+ },
33
+ "3d-basic": (dir, name) => {
34
+ writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
35
+ writeFileSync(join(dir, "main.tscn"), basicScene3D());
36
+ },
37
+ };
38
+ /**
39
+ * Copy the autopilot verification scaffold into a fresh project.
40
+ *
41
+ * Every project gets a working example of how to prove its own gameplay: a probe
42
+ * that drives the player with real input, saves rendered frames, asserts, and
43
+ * exits. Without it every agent reinvents verification from scratch, and most
44
+ * settle for asking the user to play the game instead.
45
+ *
46
+ * Never overwrites — an existing tests/autopilot/ is the user's, not ours.
47
+ */
48
+ export function scaffoldAutopilot(projectDir) {
49
+ const source = join(PACKAGE_ROOT, "assets", "autopilot");
50
+ const target = join(projectDir, "tests", "autopilot");
51
+ if (!existsSync(source) || existsSync(target))
52
+ return false;
53
+ cpSync(source, target, { recursive: true });
54
+ return true;
55
+ }
56
+ /** A folder `summer dev` may create a project in: missing, empty, or only
57
+ * dotfiles (.git, .claude, .vscode, ...). */
58
+ export function isEmptyProjectFolder(dir) {
59
+ if (!existsSync(dir))
60
+ return true;
61
+ if (!statSync(dir).isDirectory())
62
+ return false;
63
+ // .summer is Summer's own folder (in the home folder, its config): never a place to create in.
64
+ return readdirSync(dir).every((name) => name.startsWith(".") && name !== ".summer");
65
+ }
66
+ /** The home folder or a filesystem root: never a project folder by default. */
67
+ export function isHomeOrRoot(dir, home = homedir()) {
68
+ const full = resolve(dir);
69
+ return full === resolve(home) || full === resolve(full, "/");
70
+ }
71
+ /** The project that holds `dir` (project.godot here or above, never above the home folder), or null. */
72
+ export function projectRootAbove(dir, home = homedir()) {
73
+ let current = resolve(dir);
74
+ while (true) {
75
+ if (existsSync(join(current, "project.godot")))
76
+ return current;
77
+ const parent = dirname(current);
78
+ if (parent === current || current === resolve(home))
79
+ return null;
80
+ current = parent;
81
+ }
82
+ }
83
+ /**
84
+ * Write a project from a template into `fullPath`: generate a built-in, or
85
+ * fetch a pinned template (verified tree digest), then the autopilot
86
+ * scaffold, `.summer/project.json` and the agent instruction files.
87
+ * `fullPath` may already exist when isEmptyProjectFolder says so; files that
88
+ * are already there are kept, never replaced.
89
+ */
90
+ export async function createProject(entry, fullPath, options) {
91
+ const { log } = options;
92
+ // The usage line names the catalog template and its library domains, never the project name or folder.
93
+ noteToolUsage({ templateId: entry.slug, templateTags: templateTags(entry.slug) });
94
+ if (entry.builtin) {
95
+ const generate = BUILTIN_GENERATORS[entry.slug];
96
+ if (!generate)
97
+ throw new Error(`${entry.id} is declared builtin but this CLI has no generator for it. Update the CLI.`);
98
+ log(`Creating project from built-in '${entry.slug}' template...`);
99
+ mkdirSync(fullPath, { recursive: true });
100
+ generate(fullPath, options.name);
101
+ const scaffolded = scaffoldAutopilot(fullPath);
102
+ writeProjectManifest(fullPath, {
103
+ template: { id: entry.id, version: entry.version, builtin: true },
104
+ toolkit_version: toolkitVersion,
105
+ engine_version: await detectEngineVersion(),
106
+ });
107
+ return { scaffolded, instructions: writeAgentInstructions(fullPath) };
108
+ }
109
+ const pin = entry.pin;
110
+ // The fetch needs a folder that does not exist yet: into an existing
111
+ // (empty) folder, fetch next to its files and move them up.
112
+ const staging = existsSync(fullPath) ? join(fullPath, `.summer-create-${process.pid}`) : null;
113
+ const materialized = await fetchPinnedTemplate(entry, { targetDir: staging ?? fullPath, keepGit: options.keepGit, log });
114
+ if (staging) {
115
+ for (const name of readdirSync(staging)) {
116
+ const mine = join(fullPath, name);
117
+ if (!existsSync(mine))
118
+ renameSync(join(staging, name), mine);
119
+ else if (name === ".gitignore") {
120
+ // Keep both: the template's lines (.godot/ and the rest) after the folder's own.
121
+ const have = new Set(readFileSync(mine, "utf8").split(/\r?\n/));
122
+ const add = readFileSync(join(staging, name), "utf8").split(/\r?\n/).filter((line) => line.trim() && !have.has(line));
123
+ if (add.length)
124
+ appendFileSync(mine, `\n# From the ${entry.slug} template\n${add.join("\n")}\n`);
125
+ log(`Added the template's .gitignore lines to the existing .gitignore.`);
126
+ }
127
+ else
128
+ log(`Kept the existing ${name}; the template's ${name} was not used.`);
129
+ }
130
+ rmSync(staging, { recursive: true, force: true });
131
+ }
132
+ const scaffolded = scaffoldAutopilot(fullPath);
133
+ writeProjectManifest(fullPath, {
134
+ template: {
135
+ id: entry.id,
136
+ version: entry.version,
137
+ repo: pin.repo,
138
+ commit: materialized.commit,
139
+ tree_digest: materialized.tree_digest,
140
+ },
141
+ toolkit_version: toolkitVersion,
142
+ engine_version: await detectEngineVersion(),
143
+ });
144
+ log(`Source: ${pin.repo} @ ${materialized.commit.slice(0, 12)} (tree digest verified)`);
145
+ return { scaffolded, instructions: writeAgentInstructions(fullPath) };
146
+ }
147
+ /**
148
+ * project.godot uses C-style double-quoted strings; a project name containing
149
+ * `"` or `\` must be escaped or the file does not parse.
150
+ */
151
+ export function escapeGodotString(value) {
152
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, "\\n");
153
+ }
154
+ export function renderProjectSettings(name, mainScene) {
155
+ return `; Summer Engine Project
156
+ ; Generated by summer-engine CLI
157
+ ; Technical base ${SUMMER_ENGINE_COMPATIBILITY.currentTechnicalBaseVersion}; Summer follows upstream continuously
158
+
159
+ [application]
160
+
161
+ config/name="${escapeGodotString(name)}"
162
+ run/main_scene="${escapeGodotString(mainScene)}"
163
+ config/features=PackedStringArray("${SUMMER_ENGINE_COMPATIBILITY.projectFeatureTag}")
164
+
165
+ [rendering]
166
+
167
+ renderer/rendering_method="forward_plus"
168
+ `;
169
+ }
170
+ function emptyScene() {
171
+ return `[gd_scene format=3]
172
+
173
+ [node name="World" type="Node3D"]
174
+ `;
175
+ }
176
+ /** A small scene that already looks like a game: procedural sky on a real
177
+ * Environment (ACES, sky ambient light), a sun with soft shadows, a tinted
178
+ * ground and one lit hero, framed by the camera. */
179
+ function basicScene3D() {
180
+ return `[gd_scene load_steps=8 format=3]
181
+
182
+ [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
183
+ sky_top_color = Color(0.25, 0.45, 0.8, 1)
184
+ sky_horizon_color = Color(0.7, 0.78, 0.88, 1)
185
+ ground_bottom_color = Color(0.16, 0.17, 0.15, 1)
186
+ ground_horizon_color = Color(0.7, 0.78, 0.88, 1)
187
+
188
+ [sub_resource type="Sky" id="Sky_main"]
189
+ sky_material = SubResource("ProceduralSkyMaterial_sky")
190
+
191
+ [sub_resource type="Environment" id="Environment_main"]
192
+ background_mode = 2
193
+ sky = SubResource("Sky_main")
194
+ ambient_light_source = 3
195
+ reflected_light_source = 2
196
+ tonemap_mode = 3
197
+
198
+ [sub_resource type="PlaneMesh" id="PlaneMesh_ground"]
199
+ size = Vector2(200, 200)
200
+
201
+ [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ground"]
202
+ albedo_color = Color(0.35, 0.52, 0.3, 1)
203
+ roughness = 0.95
204
+
205
+ [sub_resource type="CapsuleMesh" id="CapsuleMesh_hero"]
206
+
207
+ [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hero"]
208
+ albedo_color = Color(0.95, 0.45, 0.2, 1)
209
+ roughness = 0.45
210
+
211
+ [node name="World" type="Node3D"]
212
+
213
+ [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
214
+ environment = SubResource("Environment_main")
215
+
216
+ [node name="Sun" type="DirectionalLight3D" parent="."]
217
+ transform = Transform3D(0.536875, -0.636738, 0.55347, 0, 0.656033, 0.754732, -0.843661, -0.405197, 0.352208, 0, 10, 0)
218
+ light_angular_distance = 0.5
219
+ shadow_enabled = true
220
+ directional_shadow_max_distance = 40.0
221
+
222
+ [node name="Ground" type="MeshInstance3D" parent="."]
223
+ mesh = SubResource("PlaneMesh_ground")
224
+ surface_material_override/0 = SubResource("StandardMaterial3D_ground")
225
+
226
+ [node name="Hero" type="MeshInstance3D" parent="."]
227
+ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
228
+ mesh = SubResource("CapsuleMesh_hero")
229
+ surface_material_override/0 = SubResource("StandardMaterial3D_hero")
230
+
231
+ [node name="Camera3D" type="Camera3D" parent="."]
232
+ transform = Transform3D(0.810679, -0.128625, 0.571187, 0, 0.97557, 0.219687, -0.585491, -0.178096, 0.790875, 2.6, 1.9, 3.6)
233
+ `;
234
+ }
@@ -15,10 +15,16 @@ export const CLI_AREAS = [
15
15
  /**
16
16
  * Operations that run in this package: everything in `engine`, the library
17
17
  * (`docs`, apart from the Studio workflows, which are cloud), opening a
18
- * page or an editor surface, docking an App in the editor, and uploading a
19
- * local export. Everything else runs on the hosted server.
18
+ * page or an editor surface, docking an App in the editor, uploading a
19
+ * local export, taking a store game live and filling its store page
20
+ * sections (store.go_live, store.set_details), syncing the project's
21
+ * summer.items.json, and making a
22
+ * piece of content from footage on this computer (content.make; Summer's
23
+ * server keeps the request it reports to), and creating and opening a
24
+ * project on this computer (projects.create_local, projects.open_local).
25
+ * Everything else runs on the hosted server.
20
26
  */
21
- const LOCAL_OPS = new Set(["docs.search", "docs.read", "docs.feedback", "docs.plan", "docs.engine_api", "studio.open", "apps.dock", "apps.publish_folder", "store.upload_file"]);
27
+ const LOCAL_OPS = new Set(["docs.search", "docs.read", "docs.feedback", "docs.plan", "docs.engine_api", "studio.open", "apps.dock", "apps.publish_folder", "store.upload_file", "store.go_live", "store.set_details", "economy.sync_items", "content.make", "projects.create_local", "projects.open_local"]);
22
28
  export function isLocalOp(op) {
23
29
  return op.startsWith("engine.") || LOCAL_OPS.has(op);
24
30
  }
@@ -43,6 +43,15 @@ export declare function localInputSchema(op: string): Record<string, unknown> |
43
43
  * leaves the machine as an operation name.
44
44
  */
45
45
  export declare function knownOp(op: string | null | undefined): string | null;
46
+ /**
47
+ * The operation `summer <area> <action>` runs. Agents often type a 3.x tool
48
+ * name as the action (`summer engine summer_get_scene_tree`, `summer engine
49
+ * camera-bookmark`, `summer engine search_library`): those resolve through
50
+ * legacy-names.json, so a 3.x name runs instead of failing not_found.
51
+ */
52
+ export declare function resolveCliOp(area: string, action: string): string;
53
+ /** The area's real actions nearest to what was typed, nearest first (kebab-case, as typed on the CLI). */
54
+ export declare function closestActions(op: string, limit?: number): string[];
46
55
  export declare function localWireForm(op: string): Record<string, unknown> | null;
47
56
  /**
48
57
  * The first problem a JSON Schema's top level finds in an input: a missing
@@ -3,9 +3,9 @@ import { join } from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { loadLibraryIndex } from "../library-search.js";
5
5
  import { PACKAGE_ROOT } from "../package-root.js";
6
- import { CONTRACT_VERSION, isLocalOp, legacyNames, LOCAL_RUNNERS, mcpNameOf, MIN_APPROVAL } from "./contract.js";
6
+ import { CONTRACT_VERSION, isLocalOp, legacyNames, LOCAL_RUNNERS, mcpNameOf, MIN_APPROVAL, opFromCli, resolveOpName } from "./contract.js";
7
7
  import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, splitInputSchema } from "./engine.js";
8
- import { shippedTitle } from "./public-catalog.js";
8
+ import { shippedCloudEntries, shippedTitle } from "./public-catalog.js";
9
9
  /** A local tool's effect from its registry authority: scene edits and project files are writes. */
10
10
  function effectOf(authority) {
11
11
  if (authority?.publish)
@@ -19,11 +19,23 @@ const titleOf = (summary, op) => {
19
19
  return sentence && sentence.length <= 60 ? sentence : op.replace(/^[a-z]+\./, "").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
20
20
  };
21
21
  let entriesCache = null;
22
+ /**
23
+ * The local operations that reach Summer with the person's sign-in (the
24
+ * account and the store sign-in `summer login` makes; `summer login --store`
25
+ * for the store sign-in alone). Every other local one works signed out.
26
+ */
27
+ const SIGNED_IN_LOCAL_OPS = new Set(["economy.sync_items", "store.upload_file", "store.go_live", "store.set_details", "apps.publish_folder", "content.make"]);
28
+ /**
29
+ * Local operations that change Summer's records although their registry
30
+ * authority names no local file or publish: store.set_details saves the store
31
+ * page draft (players see nothing until it goes live).
32
+ */
33
+ const REMOTE_WRITE_LOCAL_OPS = new Set(["store.set_details"]);
22
34
  function entryFor(op, toolName, tool, legacy) {
23
35
  // Engine operations carry their own title, summary and effect (engine.ts);
24
36
  // other local operations fall back to the descriptor.
25
37
  const def = engineOpDef(op);
26
- const effect = def?.effect ?? effectOf(tool.authority);
38
+ const effect = def?.effect ?? (REMOTE_WRITE_LOCAL_OPS.has(op) ? "write" : effectOf(tool.authority));
27
39
  const summary = def?.summary ?? (tool.summary ?? "").slice(0, 300);
28
40
  return {
29
41
  name: op,
@@ -118,6 +130,46 @@ export function knownOp(op) {
118
130
  return null;
119
131
  return localEntries().some((entry) => entry.name === op) || shippedTitle(op) !== undefined ? op : null;
120
132
  }
133
+ /**
134
+ * The operation `summer <area> <action>` runs. Agents often type a 3.x tool
135
+ * name as the action (`summer engine summer_get_scene_tree`, `summer engine
136
+ * camera-bookmark`, `summer engine search_library`): those resolve through
137
+ * legacy-names.json, so a 3.x name runs instead of failing not_found.
138
+ */
139
+ export function resolveCliOp(area, action) {
140
+ const op = opFromCli(area, action);
141
+ if (knownOp(op))
142
+ return op;
143
+ const [cleanArea, word] = op.split(".");
144
+ const bare = word.replace(/^summer_/, "").replace(new RegExp(`^${cleanArea}_`), "");
145
+ for (const candidate of [`${cleanArea}.${bare}`, resolveOpName(`summer_${bare}`), resolveOpName(word)]) {
146
+ if (knownOp(candidate))
147
+ return candidate;
148
+ }
149
+ return op;
150
+ }
151
+ function editDistance(a, b) {
152
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
153
+ for (let i = 1; i <= a.length; i++) {
154
+ const current = [i];
155
+ for (let j = 1; j <= b.length; j++) {
156
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
157
+ }
158
+ previous = current;
159
+ }
160
+ return previous[b.length];
161
+ }
162
+ /** The area's real actions nearest to what was typed, nearest first (kebab-case, as typed on the CLI). */
163
+ export function closestActions(op, limit = 3) {
164
+ const [area, action = ""] = op.split(".");
165
+ const typed = action.replace(/^summer_/, "");
166
+ const actions = new Set([...localEntries(), ...shippedCloudEntries()].filter((entry) => entry.name.startsWith(`${area}.`)).map((entry) => entry.name.slice(area.length + 1)));
167
+ const score = (candidate) => (candidate.includes(typed) || typed.includes(candidate) ? 0 : editDistance(typed, candidate));
168
+ return [...actions]
169
+ .sort((a, b) => score(a) - score(b) || a.localeCompare(b))
170
+ .slice(0, limit)
171
+ .map((candidate) => candidate.replace(/_/g, "-"));
172
+ }
121
173
  export function localWireForm(op) {
122
174
  const entry = localEntries().find((candidate) => candidate.name === op);
123
175
  if (!entry)
@@ -135,7 +187,7 @@ export function localWireForm(op) {
135
187
  effect: entry.effect,
136
188
  approval: entry.approval,
137
189
  provider: entry.provider,
138
- auth: def?.auth ?? "none",
190
+ auth: def?.auth ?? (SIGNED_IN_LOCAL_OPS.has(op) ? "account" : "none"),
139
191
  input: localInputSchema(op) ?? { type: "object" },
140
192
  output: { type: "object" },
141
193
  ...(legacy.length ? { legacy } : {}),
@@ -7,7 +7,8 @@ import { type Answer, type NextStep } from "./contract.js";
7
7
  * `details.tool`, so nothing the 3.x result said is lost.
8
8
  */
9
9
  export declare const ENGINE_NOT_RUNNING_STEP: NextStep;
10
- export declare function engineNotRunning(): Answer;
10
+ /** `recovery`: the 3.x result's own next step (not installed, no project, still starting, ...). */
11
+ export declare function engineNotRunning(recovery?: string): Answer;
11
12
  /** The text blocks of a tool result: the first is its payload, the rest are notes (a tip, a caption). */
12
13
  export declare function textsOf(result: unknown): string[];
13
14
  /**
@@ -18,6 +19,8 @@ export declare function engineFailure(text: string, op: string, options: {
18
19
  effect: "read" | "write";
19
20
  guide?: string;
20
21
  }): Answer;
22
+ /** A local tool's coded failure (`{ok:false, code, error, recovery}`) as the answer, with its recovery as the next step; null when it has no such code. */
23
+ export declare function codedLocalFailure(body: Record<string, unknown>): Answer | null;
21
24
  /** A 3.x engine tool's whole result as the answer: its payload on success, the mapped failure otherwise. */
22
25
  export declare function answerOfEngineResult(result: unknown, op: string, options: {
23
26
  effect: "read" | "write";
@@ -9,8 +9,9 @@ import { errorAnswer } from "./contract.js";
9
9
  export const ENGINE_NOT_RUNNING_STEP = {
10
10
  say: "Open the project in Summer Engine, or run: npx -y summer-engine@latest run <project-path>",
11
11
  };
12
- export function engineNotRunning() {
13
- return errorAnswer("engine_not_running", "Summer Engine is not running, or no project is open.", { next: ENGINE_NOT_RUNNING_STEP });
12
+ /** `recovery`: the 3.x result's own next step (not installed, no project, still starting, ...). */
13
+ export function engineNotRunning(recovery) {
14
+ return errorAnswer("engine_not_running", "Summer Engine is not running, or no project is open.", { next: recovery ? { say: recovery } : ENGINE_NOT_RUNNING_STEP });
14
15
  }
15
16
  /** The text blocks of a tool result: the first is its payload, the rest are notes (a tip, a caption). */
16
17
  export function textsOf(result) {
@@ -64,7 +65,7 @@ export function engineFailure(text, op, options) {
64
65
  const code = str(body?.code);
65
66
  const reason = str(body?.failure_reason);
66
67
  if (code === "engine_not_running" || NOT_RUNNING.test(text))
67
- return engineNotRunning();
68
+ return engineNotRunning(code === "engine_not_running" ? str(body?.recovery) : undefined);
68
69
  if (SEVERAL_EDITORS.test(text))
69
70
  return errorAnswer("conflict", message, { next: SEVERAL_EDITORS_STEP, details });
70
71
  if (NO_PROJECT.test(text))
@@ -106,6 +107,18 @@ export function engineFailure(text, op, options) {
106
107
  return errorAnswer("not_found", message, { details });
107
108
  return errorAnswer("invalid_input", message, { next: describe, details });
108
109
  }
110
+ /** The codes a local tool's failure keeps as they are (projects.create_local, projects.open_local). */
111
+ const LOCAL_CODES = new Set(["engine_not_running", "invalid_input", "not_found", "unavailable", "conflict", "unsupported"]);
112
+ /** A local tool's coded failure (`{ok:false, code, error, recovery}`) as the answer, with its recovery as the next step; null when it has no such code. */
113
+ export function codedLocalFailure(body) {
114
+ const code = str(body.code);
115
+ if (body.ok !== false || !code || !LOCAL_CODES.has(code))
116
+ return null;
117
+ const recovery = str(body.recovery);
118
+ if (code === "engine_not_running")
119
+ return engineNotRunning(recovery);
120
+ return errorAnswer(code, str(body.error) ?? str(body.message) ?? "The operation failed.", { ...(recovery ? { next: { say: recovery } } : {}), details: { tool: body } });
121
+ }
109
122
  /** A 3.x engine tool's whole result as the answer: its payload on success, the mapped failure otherwise. */
110
123
  export function answerOfEngineResult(result, op, options) {
111
124
  const [first = "", ...notes] = textsOf(result);
@@ -270,7 +270,7 @@ export const ENGINE_OPS = {
270
270
  // ── Play, stop, runtime ───────────────────────────────────────────────────
271
271
  "engine.play": {
272
272
  title: "Play the game",
273
- summary: "Start the game quietly (the editor keeps its tab and focus): the main scene or a given one, embedded or as a hidden offscreen instance, with an optional seed and fixed frame rate, or a local multiplayer session (players). Next: engine.is_running, then engine.get_diagnostics.",
273
+ summary: "Play the game in the editor's real window (agents may; quiet: the editor keeps its tab and focus): main scene or a given one, embedded or a hidden offscreen instance, optional seed and fixed frame rate, or local multiplayer (players). Next: engine.is_running, engine.get_diagnostics.",
274
274
  effect: "write",
275
275
  example: { input: { instance: "a", mode: "offscreen", deterministic: true, seed: 7 }, note: "A hidden, repeatable test run." },
276
276
  },
@@ -352,7 +352,7 @@ export const ENGINE_OPS = {
352
352
  // ── Seeing ────────────────────────────────────────────────────────────────
353
353
  "engine.screenshot": {
354
354
  title: "Take a screenshot",
355
- summary: "Return a frame as an image you see: the editor viewport, an offscreen render of a scene (preset, camera, bookmark or free pose, numbered marks) or the running game. A blank viewport frame means no redraw yet: capture again.",
355
+ summary: "Return a frame as an image you see, to check your work by eye: the editor viewport, an offscreen render of a scene (preset, camera, bookmark or free pose, numbered marks) or the running game. A blank viewport frame means no redraw yet: capture again.",
356
356
  effect: "read",
357
357
  example: { input: { target: "scene", scenePath: MAIN, framing: "camera" }, note: "The level through its own camera and lights." },
358
358
  },
@@ -385,7 +385,7 @@ export const ENGINE_OPS = {
385
385
  },
386
386
  "engine.frame_shot": {
387
387
  title: "Find camera poses for a shot",
388
- summary: "Find, score and save camera poses for a shot type (establishing, eye level, low angle, detail, corridor), tested against the real geometry. Returns the best 3 poses and a sheet, and saves the best as a bookmark.",
388
+ summary: "Find, score and save camera poses for a shot type (establishing, eye level, low angle, detail, corridor), tested against the real geometry in the open editor. Returns the best 3 poses and a sheet to look at, and saves the best as a bookmark.",
389
389
  effect: "write",
390
390
  example: { input: { scenePath: MAIN, shot: "establishing", subject: ["Town"] }, note: "A wide shot of the town." },
391
391
  },
@@ -638,7 +638,7 @@ export function splitInputSchema(def, toolSchema) {
638
638
  additionalProperties: false,
639
639
  };
640
640
  }
641
- const OP_REF = /\b(engine|docs|store)\.[a-z][a-z0-9_]*\b/g;
641
+ const OP_REF = /\b(engine|docs|store|projects)\.[a-z][a-z0-9_]*\b/g;
642
642
  /** Every operation a summary names (`engine.snapshot_diff`, `docs.read`). */
643
643
  export function opsNamedIn(text) {
644
644
  return [...new Set(text.match(OP_REF) ?? [])];
@@ -73,6 +73,11 @@ export declare class FileSaver {
73
73
  private targetOf;
74
74
  /** Download one file into the target under a free name; notes what changed from the caller's file name. */
75
75
  private download;
76
- /** The file in a hidden temp file in `dir` (engines skip dot files), from a public https host, within the size limit. */
77
76
  private fetchToTemp;
78
77
  }
78
+ /** The file in a hidden temp file in `dir` (engines skip dot files), from a public https host, within the size limit. */
79
+ export declare function fetchPublicToTemp(url: string, dir: string, options?: Pick<FileSaverOptions, "fetch" | "lookup" | "maxBytes">): Promise<{
80
+ temp: string;
81
+ finalUrl: URL;
82
+ contentType?: string;
83
+ }>;