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,282 @@
1
+ /**
2
+ * Opening a project in Summer Engine for an agent: what the MCP does when no
3
+ * editor is reachable (src/mcp/server.ts getClient), and the
4
+ * projects.open_local operation (core/capabilities/projects-local.ts), for
5
+ * agents with no shell.
6
+ *
7
+ * installed, a project, nothing open -> launch it (background), wait, connect
8
+ * not installed -> install, only with the person's yes
9
+ * (projects.open_local install:true, or `summer dev`)
10
+ * no project for this session -> projects.create_local (or `summer dev`)
11
+ * another project open -> name that project
12
+ *
13
+ * SUMMER_AUTO_LAUNCH=0 turns the launch on a tool call off; projects.open_local
14
+ * is an explicit ask and still opens.
15
+ */
16
+ import { execSync } from "child_process";
17
+ import { realpathSync } from "fs";
18
+ import { resolve, sep } from "path";
19
+ import { EngineApiClient } from "./api-client.js";
20
+ import { checkEngineHealth, findProjectRoot, listEngineInstances } from "./engine.js";
21
+ import { CLOSED_EARLY, lastLaunch, LaunchLockBusyError, launchEditor, resolveRunBinary } from "./engine-launch.js";
22
+ import { ENGINE_BIN_ENV } from "./engine-install.js";
23
+ import { installEngine } from "./engine-installer.js";
24
+ import { EngineNotRunningError } from "./error-codes.js";
25
+ import { processIsAlive } from "./util/process.js";
26
+ import { settlesWithin, sleep } from "./util/sleep.js";
27
+ export const AUTO_LAUNCH_ENV = "SUMMER_AUTO_LAUNCH";
28
+ /** One tool call ends within this, all steps included (under a 60 s client timeout). */
29
+ export const SELF_START_WAIT_MS = 40_000;
30
+ /** A launch this recent is still starting: wait for it, never launch a second editor. */
31
+ const LAUNCH_GRACE_MS = 5 * 60_000;
32
+ export const DEV_COMMAND = "npx -y summer-engine@latest dev";
33
+ export const SELF_START_RECOVERY = {
34
+ notInstalledHere: "Summer Engine is not installed. Ask the person first: this downloads and installs Summer Engine (about 660 MB, free). " +
35
+ `With their OK, call projects.open_local with install: true, or run: ${DEV_COMMAND} --yes (it installs the engine and opens this project).`,
36
+ notInstalled: "Summer Engine is not installed and this session has no project. Ask the person first: this downloads and installs Summer Engine " +
37
+ "(about 660 MB, free). With their OK: projects.create_local, then projects.open_local with install: true; " +
38
+ `or run: ${DEV_COMMAND} --yes (it installs, creates a starter project and opens it).`,
39
+ noProject: "No Summer project for this session. If summer dev is still opening one, wait and call again. Otherwise projects.create_local " +
40
+ `makes one (3d-basic by default) and projects.open_local opens it; or run: ${DEV_COMMAND} 3d-basic my-game.`,
41
+ busy: "Summer Engine has the project open but does not answer (busy, or still importing). Call again in 30 seconds.",
42
+ launchBusy: "Another Summer launch is in progress. Call again in 30 seconds.",
43
+ noDisplay: "This Linux session has no display (DISPLAY or WAYLAND_DISPLAY), so the editor cannot open here. " +
44
+ "Open the project in Summer Engine on a computer with a desktop.",
45
+ otherProject: (open, wanted) => `Summer Engine has ${open} open, not ${wanted}. Switch the editor to ${wanted}, or close it and call again.`,
46
+ starting: (root) => `Summer Engine is opening ${root}; the first open imports every asset and can take a few minutes. Call again in 30 seconds.`,
47
+ installing: (line) => `Summer Engine is installing (${line}). Call projects.open_local again in 30 seconds; it opens the project when the install is done.`,
48
+ closedEarly: (root) => `Summer Engine closed before it opened ${root}. Call again; if it closes again, open the project in Summer Engine to see why.`,
49
+ launchFailed: (code) => `Summer Engine could not start (${code}). Repair it: npx -y summer-engine@latest install --yes, then call again.`,
50
+ autoLaunchOff: (root) => `Open it with projects.open_local, or run: npx -y summer-engine@latest run ${root}`,
51
+ };
52
+ /** Projects this process launched, by root: when and which process, so a slow first start is waited on while it lives. */
53
+ const launched = new Map();
54
+ /** The answer just given: a caller's immediate retry (withEngine retries a
55
+ * failed connect once) gets it again instead of a second wait. */
56
+ let recent = null;
57
+ const RECENT_MS = 3_000;
58
+ /** The engine install this process runs for projects.open_local; it outlives
59
+ * one tool call, and the next call reports it or carries on. */
60
+ let install = null;
61
+ /** Test-only: forget the last answer only (withEngine's immediate retry window). */
62
+ export function _resetRecentForTests() {
63
+ recent = null;
64
+ }
65
+ /** Test-only: forget the launches and the install this process made. */
66
+ export function _resetSelfStartForTests() {
67
+ launched.clear();
68
+ recent = null;
69
+ install = null;
70
+ }
71
+ /**
72
+ * The project this session works on: --project, else the folder the agent
73
+ * started in, else the project the last `summer dev` / `summer run` opened
74
+ * from this folder (it makes a subfolder the agent did not start in), while
75
+ * that launch is recent, its editor lives and the project is still there.
76
+ */
77
+ export async function sessionProject(selection) {
78
+ if (selection?.projectPath)
79
+ return findProjectRoot(selection.projectPath);
80
+ if (selection?.instanceId)
81
+ return null;
82
+ const cwd = selection?.cwd ?? process.cwd();
83
+ const here = await findProjectRoot(cwd);
84
+ if (here)
85
+ return here;
86
+ const last = lastLaunch();
87
+ if (!last)
88
+ return null;
89
+ const root = await findProjectRoot(last.projectPath);
90
+ const folder = canonicalPath(cwd);
91
+ // Only a launch from this session's folder, or of a project below it: never another session's project.
92
+ const ours = root && (last.cwd === folder || root.startsWith(folder.endsWith(sep) ? folder : folder + sep));
93
+ return ours ? root : null;
94
+ }
95
+ function canonicalPath(path) {
96
+ try {
97
+ return realpathSync(path);
98
+ }
99
+ catch {
100
+ return resolve(path);
101
+ }
102
+ }
103
+ /** Linux with no display server: an editor window cannot open (containers, SSH, cloud agents). */
104
+ export function hasNoDisplay(env = process.env, platform = process.platform) {
105
+ return platform === "linux" && !env.DISPLAY?.trim() && !env.WAYLAND_DISPLAY?.trim();
106
+ }
107
+ /** A live editor with another project than `root`, named for the person. */
108
+ async function otherOpenProject(root) {
109
+ for (const instance of await listEngineInstances()) {
110
+ if (instance.resourceRoot === root)
111
+ continue;
112
+ if (await checkEngineHealth(instance.port)) {
113
+ return instance.projectName ? `${instance.projectName} (${instance.resourceRoot})` : instance.resourceRoot;
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ async function connectWithin(selection, connect, ms) {
119
+ const deadline = Date.now() + ms;
120
+ while (true) {
121
+ try {
122
+ return await connect(selection);
123
+ }
124
+ catch {
125
+ if (Date.now() >= deadline)
126
+ return null;
127
+ await sleep(1_000);
128
+ }
129
+ }
130
+ }
131
+ /** The MCP's stdout is its protocol: the installer never prints to it or hands it to a child. */
132
+ const quietInstall = (opts) => installEngine(opts, { stdoutIsTTY: false, exec: (command) => execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }) });
133
+ /**
134
+ * Install the engine in the background of this process and wait up to
135
+ * `waitMs` for it: true when it is installed, false while it still runs.
136
+ * A failed install throws its EngineInstallError (once; the next call starts over).
137
+ */
138
+ async function installWithin(deps, waitMs) {
139
+ if (!install) {
140
+ const state = { line: "starting", done: false, error: null, promise: Promise.resolve() };
141
+ state.promise = (deps.install ?? quietInstall)({ log: (line) => (state.line = line.trim() || state.line), isTTY: false }).then(() => {
142
+ state.done = true;
143
+ }, (error) => {
144
+ state.done = true;
145
+ state.error = error;
146
+ });
147
+ install = state;
148
+ }
149
+ const current = install;
150
+ await settlesWithin(current.promise, waitMs);
151
+ if (!current.done)
152
+ return false;
153
+ install = null;
154
+ if (current.error)
155
+ throw current.error;
156
+ return true;
157
+ }
158
+ /**
159
+ * Open `root` in the editor and return a connected client: launch it unless
160
+ * an editor has it, wait, and never launch it twice. Throws an
161
+ * EngineNotRunningError whose recovery is the one next step for the case.
162
+ */
163
+ export async function openProjectInEditor(root, selection, options = {}, deps = {}) {
164
+ const env = deps.env ?? process.env;
165
+ const now = deps.now ?? Date.now;
166
+ const connect = deps.connect ?? ((s) => EngineApiClient.connect(s));
167
+ const log = deps.log ?? (() => { });
168
+ // One deadline for the whole call: install wait, launch, --help probe and connect.
169
+ const deadline = Date.now() + (deps.waitMs ?? SELF_START_WAIT_MS);
170
+ const left = (reserve = 0) => Math.max(0, deadline - Date.now() - reserve);
171
+ const reason = options.notRunning?.reason ?? "no editor has the project open";
172
+ const failure = (message, recovery) => new EngineNotRunningError(message, reason, recovery);
173
+ if (hasNoDisplay(env))
174
+ throw failure("Summer Engine cannot open a window here.", SELF_START_RECOVERY.noDisplay);
175
+ const open = await otherOpenProject(root);
176
+ if (open)
177
+ throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(open, root));
178
+ const binary = resolveRunBinary(undefined, env);
179
+ if (binary.binary === null) {
180
+ // A build named in SUMMER_BIN that is missing is not "not installed": installing would not change it.
181
+ if (env[ENGINE_BIN_ENV]?.trim())
182
+ throw failure("Summer Engine could not start.", binary.error);
183
+ if (!options.install && !install)
184
+ throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
185
+ log("Summer Engine is not installed: installing it");
186
+ if (!(await installWithin(deps, left(5_000)))) {
187
+ throw failure("Summer Engine is installing.", SELF_START_RECOVERY.installing(install?.line ?? "starting"));
188
+ }
189
+ }
190
+ const since = launched.get(root);
191
+ if (since && now() - since.at < LAUNCH_GRACE_MS && (since.pid === undefined || processIsAlive(since.pid))) {
192
+ log(`waiting for the editor launched ${Math.round((now() - since.at) / 1000)} s ago for ${root}`);
193
+ const client = await connectWithin(selection, connect, left());
194
+ if (client)
195
+ return client;
196
+ throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
197
+ }
198
+ launched.delete(root);
199
+ log(`no editor has ${root} open: launching it in the background`);
200
+ let result;
201
+ try {
202
+ result = await (deps.launch ?? launchEditor)({
203
+ projectPath: root,
204
+ posture: "background",
205
+ // The lock wait, the --help probe and the API wait end by then; the connect after it fits in the rest.
206
+ deadline: deadline - 5_000,
207
+ out: { log, error: log },
208
+ });
209
+ }
210
+ catch (error) {
211
+ // Another launch holds the lock: it may be for another project.
212
+ if (error instanceof LaunchLockBusyError)
213
+ throw failure("Another Summer launch is in progress.", SELF_START_RECOVERY.launchBusy);
214
+ throw failure("Summer Engine could not start.", SELF_START_RECOVERY.launchFailed(error.message));
215
+ }
216
+ switch (result.status) {
217
+ case "running": {
218
+ if (result.launched)
219
+ launched.set(root, { at: now(), pid: result.pid });
220
+ // The editor answers health a moment before it registers for discovery.
221
+ const client = await connectWithin(selection, connect, Math.min(5_000, left()));
222
+ if (client)
223
+ return client;
224
+ throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
225
+ }
226
+ case "starting":
227
+ launched.set(root, { at: now(), pid: result.pid });
228
+ throw failure("Summer Engine is starting.", SELF_START_RECOVERY.starting(root));
229
+ case "other_project":
230
+ throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(result.project, root));
231
+ case "no_binary":
232
+ throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
233
+ case "spawn_failed":
234
+ if (result.code === CLOSED_EARLY)
235
+ throw failure("Summer Engine closed before the project opened.", SELF_START_RECOVERY.closedEarly(root));
236
+ throw failure("Summer Engine could not start.", SELF_START_RECOVERY.launchFailed(result.code));
237
+ }
238
+ }
239
+ /**
240
+ * Open the session's project and return a connected client, or throw an
241
+ * EngineNotRunningError whose recovery is the one next step for this case.
242
+ * Never installs: that needs the person's yes (projects.open_local).
243
+ */
244
+ export async function selfStartEngine(selection, notRunning, deps = {}) {
245
+ const now = deps.now ?? Date.now;
246
+ if (recent && now() - recent.at < RECENT_MS)
247
+ throw recent.error;
248
+ try {
249
+ return await openSessionProject(selection, notRunning, deps);
250
+ }
251
+ catch (error) {
252
+ if (error instanceof EngineNotRunningError)
253
+ recent = { at: now(), error };
254
+ throw error;
255
+ }
256
+ }
257
+ async function openSessionProject(selection, notRunning, deps) {
258
+ const env = deps.env ?? process.env;
259
+ const failure = (message, recovery) => new EngineNotRunningError(message, notRunning.reason, recovery);
260
+ // A registered editor that does not answer is busy (an import, a blocking
261
+ // script): it is there, so never start a second one.
262
+ if (/instance \S+ is not responding/.test(notRunning.reason))
263
+ throw failure("Summer Engine is not answering.", SELF_START_RECOVERY.busy);
264
+ // The person already said yes to an install this process runs: say so instead of asking again.
265
+ if (install && !install.done)
266
+ throw failure("Summer Engine is installing.", SELF_START_RECOVERY.installing(install.line));
267
+ const root = await sessionProject(selection);
268
+ if (!root) {
269
+ if (resolveRunBinary(undefined, env).binary === null)
270
+ throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalled);
271
+ throw failure("No Summer project for this session.", SELF_START_RECOVERY.noProject);
272
+ }
273
+ if (env[AUTO_LAUNCH_ENV] === "0") {
274
+ const open = await otherOpenProject(root);
275
+ if (open)
276
+ throw failure("Summer Engine has another project open.", SELF_START_RECOVERY.otherProject(open, root));
277
+ if (resolveRunBinary(undefined, env).binary === null)
278
+ throw failure("Summer Engine is not installed.", SELF_START_RECOVERY.notInstalledHere);
279
+ throw failure(notRunning.message, SELF_START_RECOVERY.autoLaunchOff(root));
280
+ }
281
+ return openProjectInEditor(root, selection, { notRunning }, deps);
282
+ }
@@ -48,7 +48,13 @@ export interface EngineConnection {
48
48
  export declare function getApiToken(summerDir?: string): Promise<string | null>;
49
49
  export declare function getApiPort(summerDir?: string): Promise<number>;
50
50
  export declare function findProjectRoot(startPath: string): Promise<string | null>;
51
- export declare function listEngineInstances(nowMs?: number, summerDir?: string): Promise<EngineInstance[]>;
51
+ export declare function listEngineInstances(nowMs?: number, summerDir?: string,
52
+ /** Keep entries whose heartbeat is old (up to 30 min): an editor that is
53
+ * alive but busy (a long import) stops beating; the launcher must still
54
+ * not start a second one. */
55
+ options?: {
56
+ anyHeartbeat?: boolean;
57
+ }): Promise<EngineInstance[]>;
52
58
  export declare function resolveEngineConnection(selection?: EngineSelection, options?: EngineDiscoveryOptions): Promise<EngineConnection>;
53
59
  /**
54
60
  * The registry half of no-selection discovery (the CLI face; see
@@ -7,6 +7,7 @@ import { parseEngineCapabilities, } from "./capability-skew.js";
7
7
  const DEFAULT_PORT = 6550;
8
8
  const INSTANCE_SCHEMA_VERSION = 1;
9
9
  const INSTANCE_STALE_MS = 180_000;
10
+ const BUSY_INSTANCE_STALE_MS = 30 * 60_000;
10
11
  const MAX_INSTANCE_FILE_BYTES = 64 * 1024;
11
12
  /** Env names the MCP server already honours for an explicit selection
12
13
  * (src/mcp/server.ts startMcpServer). The CLI face reads the same two so a
@@ -116,7 +117,11 @@ function parseEngineInstance(value) {
116
117
  engineVersion: stringFromUnknown(record.engineVersion),
117
118
  };
118
119
  }
119
- export async function listEngineInstances(nowMs = Date.now(), summerDir = getSummerDir()) {
120
+ export async function listEngineInstances(nowMs = Date.now(), summerDir = getSummerDir(),
121
+ /** Keep entries whose heartbeat is old (up to 30 min): an editor that is
122
+ * alive but busy (a long import) stops beating; the launcher must still
123
+ * not start a second one. */
124
+ options = {}) {
120
125
  const instancesDir = join(summerDir, "instances");
121
126
  try {
122
127
  const dirInfo = await lstat(instancesDir);
@@ -142,7 +147,9 @@ export async function listEngineInstances(nowMs = Date.now(), summerDir = getSum
142
147
  const parsed = parseEngineInstance(JSON.parse(await readFile(path, "utf-8")));
143
148
  if (!parsed || !processIsAlive(parsed.pid))
144
149
  continue;
145
- if (nowMs - parsed.heartbeatAt * 1000 > INSTANCE_STALE_MS)
150
+ // A busy editor's heartbeat may lag; one silent this long is gone (its pid reused).
151
+ const staleAfter = options.anyHeartbeat ? BUSY_INSTANCE_STALE_MS : INSTANCE_STALE_MS;
152
+ if (nowMs - parsed.heartbeatAt * 1000 > staleAfter)
146
153
  continue;
147
154
  parsed.resourceRoot = await canonicalPath(parsed.resourceRoot);
148
155
  instances.push(parsed);
@@ -51,8 +51,11 @@ export declare function networkDetails(error: unknown): {
51
51
  * registry paths) stays in the logs, not in the line the agent repeats. */
52
52
  export declare class EngineNotRunningError extends Error {
53
53
  readonly reason: string;
54
+ readonly recovery: string;
54
55
  readonly code: "engine_not_running";
55
- constructor(message: string, reason: string);
56
+ /** `recovery`: the one next step for this case (not installed, no project,
57
+ * another project open, still starting); the general step by default. */
58
+ constructor(message: string, reason: string, recovery?: string);
56
59
  }
57
60
  /** A connect error from core/engine.ts → EngineNotRunningError when it means
58
61
  * no editor is reachable; null for the others (several editors open, …),
@@ -71,10 +71,14 @@ export function networkDetails(error) {
71
71
  * registry paths) stays in the logs, not in the line the agent repeats. */
72
72
  export class EngineNotRunningError extends Error {
73
73
  reason;
74
+ recovery;
74
75
  code = "engine_not_running";
75
- constructor(message, reason) {
76
+ /** `recovery`: the one next step for this case (not installed, no project,
77
+ * another project open, still starting); the general step by default. */
78
+ constructor(message, reason, recovery = RECOVERY.engine_not_running) {
76
79
  super(message);
77
80
  this.reason = reason;
81
+ this.recovery = recovery;
78
82
  this.name = "EngineNotRunningError";
79
83
  }
80
84
  }
@@ -93,5 +97,5 @@ export function engineNotRunningFromConnectError(reason) {
93
97
  return null;
94
98
  }
95
99
  /** Engine-free tools keep working while the engine is down. */
96
- export const ENGINE_FREE_NOTE = "Only tools that touch the local project need the engine. Library, generation and asset tools " +
97
- "(summer_search_library, summer_generate_*, summer_search_assets, summer_list_my_assets, summer_get_asset, summer_check_job) work now.";
100
+ export const ENGINE_FREE_NOTE = "Only tools that touch the local project need the engine. Docs, generation and asset tools " +
101
+ "(summer_docs_search, summer_docs_read, summer_assets_generate_*, summer_assets_search, summer_assets_get, summer_jobs_get) work now.";
@@ -21,10 +21,10 @@ export declare const readLibraryInputSchema: z.ZodObject<{
21
21
  part: z.ZodOptional<z.ZodEnum<["skill", "resource", "all"]>>;
22
22
  }, "strict", z.ZodTypeAny, {
23
23
  id: string;
24
- part?: "resource" | "all" | "skill" | undefined;
24
+ part?: "resource" | "skill" | "all" | undefined;
25
25
  }, {
26
26
  id: string;
27
- part?: "resource" | "all" | "skill" | undefined;
27
+ part?: "resource" | "skill" | "all" | undefined;
28
28
  }>;
29
29
  export type ReadLibraryArgs = z.infer<typeof readLibraryInputSchema>;
30
30
  export interface LibraryReadDeps extends LibrarySearchDeps {
@@ -98,12 +98,12 @@ export declare const searchLibraryInputSchema: z.ZodObject<{
98
98
  include_preview: z.ZodOptional<z.ZodBoolean>;
99
99
  }, "strict", z.ZodTypeAny, {
100
100
  query: string;
101
- kinds?: ("template" | "tool" | "skill" | "reference" | "example" | "collection")[] | undefined;
101
+ kinds?: ("skill" | "tool" | "template" | "reference" | "example" | "collection")[] | undefined;
102
102
  limit?: number | undefined;
103
103
  include_preview?: boolean | undefined;
104
104
  }, {
105
105
  query: string;
106
- kinds?: ("template" | "tool" | "skill" | "reference" | "example" | "collection")[] | undefined;
106
+ kinds?: ("skill" | "tool" | "template" | "reference" | "example" | "collection")[] | undefined;
107
107
  limit?: number | undefined;
108
108
  include_preview?: boolean | undefined;
109
109
  }>;
@@ -0,0 +1,50 @@
1
+ import { type TemplateEntry } from "./templates.js";
2
+ import { type InstructionFileResult } from "../project-memory/agent-instructions.js";
3
+ /**
4
+ * Built-in templates are generated in-process — no download, no pin. Their
5
+ * library manifests (`library/templates/<slug>/resource.yaml`) declare
6
+ * `builtin: true`; this map supplies the generator for each such slug.
7
+ */
8
+ type BuiltinGenerator = (dir: string, projectName: string) => void;
9
+ export declare const BUILTIN_GENERATORS: Readonly<Record<string, BuiltinGenerator>>;
10
+ /**
11
+ * Copy the autopilot verification scaffold into a fresh project.
12
+ *
13
+ * Every project gets a working example of how to prove its own gameplay: a probe
14
+ * that drives the player with real input, saves rendered frames, asserts, and
15
+ * exits. Without it every agent reinvents verification from scratch, and most
16
+ * settle for asking the user to play the game instead.
17
+ *
18
+ * Never overwrites — an existing tests/autopilot/ is the user's, not ours.
19
+ */
20
+ export declare function scaffoldAutopilot(projectDir: string): boolean;
21
+ /** A folder `summer dev` may create a project in: missing, empty, or only
22
+ * dotfiles (.git, .claude, .vscode, ...). */
23
+ export declare function isEmptyProjectFolder(dir: string): boolean;
24
+ /** The home folder or a filesystem root: never a project folder by default. */
25
+ export declare function isHomeOrRoot(dir: string, home?: string): boolean;
26
+ /** The project that holds `dir` (project.godot here or above, never above the home folder), or null. */
27
+ export declare function projectRootAbove(dir: string, home?: string): string | null;
28
+ export interface CreatedProject {
29
+ scaffolded: boolean;
30
+ instructions: InstructionFileResult[];
31
+ }
32
+ /**
33
+ * Write a project from a template into `fullPath`: generate a built-in, or
34
+ * fetch a pinned template (verified tree digest), then the autopilot
35
+ * scaffold, `.summer/project.json` and the agent instruction files.
36
+ * `fullPath` may already exist when isEmptyProjectFolder says so; files that
37
+ * are already there are kept, never replaced.
38
+ */
39
+ export declare function createProject(entry: TemplateEntry, fullPath: string, options: {
40
+ name: string;
41
+ keepGit?: boolean;
42
+ log: (line: string) => void;
43
+ }): Promise<CreatedProject>;
44
+ /**
45
+ * project.godot uses C-style double-quoted strings; a project name containing
46
+ * `"` or `\` must be escaped or the file does not parse.
47
+ */
48
+ export declare function escapeGodotString(value: string): string;
49
+ export declare function renderProjectSettings(name: string, mainScene: string): string;
50
+ export {};