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
@@ -1,60 +1,17 @@
1
1
  import { Command } from "commander";
2
- /** Guard a download URL from the releases endpoint. A missing/empty URL (an
3
- * outdated or misconfigured endpoint) otherwise becomes a cryptic
4
- * fetch(undefined) failure — surface it clearly with a manual fallback. */
5
- export declare function requireDownloadUrl(value: string | undefined, label: string): string;
6
- /** Verify a downloaded file's sha256 against the expected hash from the releases
7
- * endpoint. Skipped when no expected hash is published. Catches a truncated or
8
- * corrupted download (a ~1GB install over a flaky network) before it becomes a
9
- * confusing DMG-mount / installer failure downstream. */
10
- export declare function verifyChecksum(actual: string, expected: string | undefined, label: string): void;
11
- import { parseMacBundleVersion, readMacBundleVersion } from "../../core/launch-posture.js";
12
- export { parseMacBundleVersion, readMacBundleVersion };
13
- export declare function isSameVersion(installed: string | null, latest: string): boolean;
14
- export type MacInstallPlan = {
15
- action: "install";
16
- } | {
17
- action: "up-to-date";
18
- version: string;
19
- } | {
20
- action: "needs-confirmation";
21
- installed: string | null;
22
- } | {
23
- action: "refuse";
24
- reason: string;
25
- };
26
- /** Decide what `summer install` may do about an existing /Applications/Summer.app.
27
- * Replacing an installed engine is destructive, so it needs --yes or a TTY
28
- * prompt; an equal version is a no-op success. */
29
- export declare function planMacInstall(input: {
30
- exists: boolean;
31
- installedVersion: string | null;
32
- latestVersion: string;
33
- yes: boolean;
34
- isTTY: boolean;
35
- }): MacInstallPlan;
36
- export type ShellExec = (command: string) => void;
37
- /** Replace destApp with the bundle on the mounted DMG WITHOUT a window where
38
- * no engine exists: copy to <destApp>.new first, and only after that
39
- * succeeds remove the old bundle and rename the staged copy into place. A
40
- * failed copy leaves the existing install untouched. */
41
- export declare function replaceMacApp(mountPoint: string, destApp: string, exec: ShellExec): void;
42
- /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
43
- * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
44
- * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
45
- * installer with "unexpected argument '/S' found" — this is the wizard bug. */
46
- export declare function windowsInstallerArgs(customPath?: string): string;
47
- /** Printed after an install: the first start of a new engine is slow and silent. */
48
- export declare const FIRST_START_NOTE: string;
49
- /** Where the Velopack installer put Summer.exe: <dir>/current/Summer.exe for --path, else the default locations. */
50
- export declare function windowsInstalledBinary(customPath?: string, exists?: (path: string) => boolean): string | null;
51
- /**
52
- * Download progress: rewrites one line on a terminal; elsewhere (an agent,
53
- * a log file) one line per 10%, so a 1 GB download is about ten lines
54
- * instead of hundreds of kilobytes.
55
- */
56
- export declare function createProgressReporter(isTTY: boolean, write: (text: string) => void): (downloaded: number, total: number) => void;
2
+ import { EngineInstallError } from "../../core/engine-install.js";
3
+ import { installEngine } from "../../core/engine-installer.js";
4
+ export * from "../../core/engine-installer.js";
5
+ /** The coded failure an error from installEngine stands for, or null for a
6
+ * bug (rethrown as before). A raw ENOSPC/EACCES/EPERM still gets its code. */
7
+ export declare function installCommandFailure(err: unknown): EngineInstallError | null;
8
+ /** `summer install`: installEngine with a printed, coded failure and exit code 1. */
9
+ export declare function runInstallCommand(opts: {
10
+ path?: string;
11
+ yes?: boolean;
12
+ }, deps?: {
13
+ install?: typeof installEngine;
14
+ error?: (line: string) => void;
15
+ noteError?: (code: string) => void;
16
+ }): Promise<void>;
57
17
  export declare const installCommand: Command;
58
- export declare function downloadFile(url: string, dest: string): Promise<{
59
- sha256: string;
60
- }>;
@@ -1,492 +1,42 @@
1
1
  import { Command } from "commander";
2
- import { execSync } from "child_process";
3
- import { createInterface } from "node:readline/promises";
4
- import { createHash } from "crypto";
5
- import { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, symlinkSync, } from "fs";
6
- import { pipeline } from "stream/promises";
7
- import { Readable } from "stream";
8
- import { tmpdir, platform } from "os";
9
- import { dirname, join, resolve } from "path";
10
- import { ENGINE_BINARY_ENV, LINUX_ENGINE_BINARY_NAME, macInstallDestination, recordEngineInstall, windowsEnginePaths, findExtractedEngineBinary, linuxArchiveKind, linuxEngineInstallDir, pickLinuxReleaseAsset, resolveLinuxInstallSource, } from "../../core/engine-install.js";
11
- const RELEASES_URL = "https://www.summerengine.com/api/desktop/releases";
12
- /** Guard a download URL from the releases endpoint. A missing/empty URL (an
13
- * outdated or misconfigured endpoint) otherwise becomes a cryptic
14
- * fetch(undefined) failure — surface it clearly with a manual fallback. */
15
- export function requireDownloadUrl(value, label) {
16
- if (!value || value.trim().length === 0) {
17
- throw new Error(`Release info is missing the ${label} download URL — the releases endpoint may be ` +
18
- `outdated. Download manually from https://summerengine.com/download`);
19
- }
20
- return value;
21
- }
22
- /** Verify a downloaded file's sha256 against the expected hash from the releases
23
- * endpoint. Skipped when no expected hash is published. Catches a truncated or
24
- * corrupted download (a ~1GB install over a flaky network) before it becomes a
25
- * confusing DMG-mount / installer failure downstream. */
26
- export function verifyChecksum(actual, expected, label) {
27
- if (!expected || expected.trim().length === 0)
28
- return; // nothing published to verify against
29
- if (actual.toLowerCase() !== expected.toLowerCase()) {
30
- throw new Error(`${label} checksum mismatch — the download was corrupted or incomplete ` +
31
- `(expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…). Re-run \`summer install\`.`);
32
- }
33
- }
34
- // The bundle-version readers live in core (src/core/launch-posture.ts) because
35
- // `summer run` needs the installed version BEFORE the engine is up to decide
36
- // whether it may pass --summer-background; re-exported here for install.test.ts.
37
- import { parseMacBundleVersion, readMacBundleVersion } from "../../core/launch-posture.js";
38
- export { parseMacBundleVersion, readMacBundleVersion };
39
- export function isSameVersion(installed, latest) {
40
- if (!installed)
41
- return false;
42
- const norm = (value) => value.trim().replace(/^v/i, "");
43
- return norm(installed) === norm(latest);
44
- }
45
- /** Decide what `summer install` may do about an existing /Applications/Summer.app.
46
- * Replacing an installed engine is destructive, so it needs --yes or a TTY
47
- * prompt; an equal version is a no-op success. */
48
- export function planMacInstall(input) {
49
- if (!input.exists)
50
- return { action: "install" };
51
- if (isSameVersion(input.installedVersion, input.latestVersion)) {
52
- return { action: "up-to-date", version: input.latestVersion };
53
- }
54
- if (input.yes)
55
- return { action: "install" };
56
- if (input.isTTY)
57
- return { action: "needs-confirmation", installed: input.installedVersion };
58
- return {
59
- action: "refuse",
60
- reason: `Summer Engine ${input.installedVersion ?? "(unknown version)"} is already installed; ` +
61
- `updating to v${input.latestVersion} replaces it. Re-run with --yes to confirm.`,
62
- };
63
- }
64
- /** Replace destApp with the bundle on the mounted DMG WITHOUT a window where
65
- * no engine exists: copy to <destApp>.new first, and only after that
66
- * succeeds remove the old bundle and rename the staged copy into place. A
67
- * failed copy leaves the existing install untouched. */
68
- export function replaceMacApp(mountPoint, destApp, exec) {
69
- const staged = `${destApp}.new`;
70
- exec(`rm -rf "${staged}"`);
71
- try {
72
- exec(`cp -R "${mountPoint}/Summer.app" "${staged}"`);
2
+ import { noteCliError } from "../../core/cli-usage.js";
3
+ import { EngineInstallError, asInstallError, errorDetail, installFailureReason } from "../../core/engine-install.js";
4
+ import { installEngine } from "../../core/engine-installer.js";
5
+ // The install itself lives in core (shared with `summer dev` and the MCP's
6
+ // projects.open_local); this file is the command.
7
+ export * from "../../core/engine-installer.js";
8
+ // ------------------------------------------------------------------ command
9
+ /** The coded failure an error from installEngine stands for, or null for a
10
+ * bug (rethrown as before). A raw ENOSPC/EACCES/EPERM still gets its code. */
11
+ export function installCommandFailure(err) {
12
+ if (err instanceof EngineInstallError)
13
+ return err;
14
+ const reason = installFailureReason(err, "copy");
15
+ return reason === "disk_space" || reason === "permission" ? asInstallError(err, reason, errorDetail) : null;
16
+ }
17
+ /** `summer install`: installEngine with a printed, coded failure and exit code 1. */
18
+ export async function runInstallCommand(opts, deps = {}) {
19
+ try {
20
+ await (deps.install ?? installEngine)({
21
+ path: opts.path,
22
+ yes: opts.yes,
23
+ log: (line) => console.log(line),
24
+ isTTY: Boolean(process.stdin.isTTY),
25
+ });
73
26
  }
74
27
  catch (err) {
75
- try {
76
- exec(`rm -rf "${staged}"`);
77
- }
78
- catch { /* best effort */ }
79
- throw err;
80
- }
81
- exec(`rm -rf "${destApp}"`);
82
- exec(`mv "${staged}" "${destApp}"`);
83
- }
84
- /** Remove partial downloads / staged bundles when the user interrupts. */
85
- function cleanupOnSignal(paths) {
86
- const handler = () => {
87
- for (const path of paths) {
88
- try {
89
- rmSync(path, { recursive: true, force: true });
90
- }
91
- catch { /* best effort */ }
92
- }
93
- process.exit(130);
94
- };
95
- process.on("SIGINT", handler);
96
- process.on("SIGTERM", handler);
97
- return () => {
98
- process.off("SIGINT", handler);
99
- process.off("SIGTERM", handler);
100
- };
101
- }
102
- async function confirm(question) {
103
- const rl = createInterface({ input: process.stdin, output: process.stdout });
104
- try {
105
- const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
106
- return answer === "y" || answer === "yes";
107
- }
108
- finally {
109
- rl.close();
28
+ const failure = installCommandFailure(err);
29
+ if (!failure)
30
+ throw err;
31
+ (deps.error ?? ((line) => console.error(line)))(failure.message);
32
+ (deps.noteError ?? noteCliError)(failure.reason);
33
+ process.exitCode = 1;
110
34
  }
111
35
  }
112
- /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
113
- * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
114
- * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
115
- * installer with "unexpected argument '/S' found" — this is the wizard bug. */
116
- export function windowsInstallerArgs(customPath) {
117
- return customPath ? `--silent --installto "${customPath}"` : "--silent";
118
- }
119
- /** Printed after an install: the first start of a new engine is slow and silent. */
120
- export const FIRST_START_NOTE = "The first start of a new engine version can take several minutes with no output " +
121
- "(the system checks the new app and the engine builds its caches). Later starts are fast.";
122
- /** Where the Velopack installer put Summer.exe: <dir>/current/Summer.exe for --path, else the default locations. */
123
- export function windowsInstalledBinary(customPath, exists = existsSync) {
124
- const candidates = customPath
125
- ? [join(customPath, "current", "Summer.exe"), join(customPath, "Summer.exe")]
126
- : windowsEnginePaths();
127
- return candidates.find((path) => exists(path)) ?? null;
128
- }
129
- /**
130
- * Download progress: rewrites one line on a terminal; elsewhere (an agent,
131
- * a log file) one line per 10%, so a 1 GB download is about ten lines
132
- * instead of hundreds of kilobytes.
133
- */
134
- export function createProgressReporter(isTTY, write) {
135
- let lastStep = -1;
136
- return (downloaded, total) => {
137
- if (total <= 0)
138
- return;
139
- const pct = Math.min(100, Math.floor((downloaded / total) * 100));
140
- const mb = (downloaded / 1024 / 1024).toFixed(1);
141
- if (isTTY) {
142
- write(`\r ${pct}% (${mb}MB)`);
143
- return;
144
- }
145
- const step = Math.floor(pct / 10);
146
- if (step > lastStep) {
147
- lastStep = step;
148
- write(` ${pct}% (${mb}MB)\n`);
149
- }
150
- };
151
- }
152
36
  export const installCommand = new Command("install")
153
37
  .description("Download and install Summer Engine")
154
38
  .option("--path <dir>", "Custom install directory (macOS: the engine goes in <dir>/Summer.app). summer remembers it, so later commands find this engine")
155
39
  .option("--yes", "Replace an already-installed engine without prompting")
156
40
  .action(async (opts) => {
157
- const os = platform();
158
- if (os === "linux") {
159
- await installLinux(opts.path);
160
- return;
161
- }
162
- if (os !== "darwin" && os !== "win32") {
163
- console.error(`Unsupported platform: ${os}.\n` +
164
- "Summer Engine installs on macOS, Windows, and Linux (x86_64).\n" +
165
- "Download from: https://summerengine.com/download");
166
- process.exit(1);
167
- }
168
- console.log("Fetching latest release info...");
169
- let releases;
170
- try {
171
- const res = await fetch(RELEASES_URL, { signal: AbortSignal.timeout(10000) });
172
- if (!res.ok)
173
- throw new Error(`HTTP ${res.status}`);
174
- releases = (await res.json());
175
- }
176
- catch (err) {
177
- console.error("Could not fetch release info. Download manually from:\n" +
178
- " https://summerengine.com/download");
179
- process.exit(1);
180
- }
181
- if (os === "darwin") {
182
- await installMac(releases, opts.path, Boolean(opts.yes));
183
- }
184
- else {
185
- await installWindows(releases, opts.path);
186
- }
41
+ await runInstallCommand(opts);
187
42
  });
188
- async function installMac(releases, customPath, yes = false) {
189
- const info = releases.latest.macos;
190
- if (!info) {
191
- console.error("No macOS release found.");
192
- process.exit(1);
193
- }
194
- const dmgUrl = requireDownloadUrl(info.dmg_url, "macOS DMG");
195
- console.log(`Latest version: ${info.version}`);
196
- const destApp = macInstallDestination(customPath);
197
- const destBinary = join(destApp, "Contents", "MacOS", "Summer");
198
- const exists = existsSync(destApp);
199
- const installedVersion = exists ? readMacBundleVersion(destApp) : null;
200
- const plan = planMacInstall({
201
- exists,
202
- installedVersion,
203
- latestVersion: info.version,
204
- yes,
205
- isTTY: Boolean(process.stdin.isTTY),
206
- });
207
- if (plan.action === "up-to-date") {
208
- await recordEngineInstall(destBinary, plan.version);
209
- console.log(`Summer Engine v${plan.version} is already installed at ${destApp} (up to date).`);
210
- return;
211
- }
212
- if (plan.action === "refuse") {
213
- console.error(plan.reason);
214
- process.exitCode = 1;
215
- return;
216
- }
217
- if (plan.action === "needs-confirmation") {
218
- console.log(`Summer Engine ${plan.installed ?? "(unknown version)"} is installed at ${destApp}.`);
219
- if (!(await confirm(`Replace it with v${info.version}?`))) {
220
- console.log("Cancelled. Nothing was changed.");
221
- return;
222
- }
223
- }
224
- const dmgPath = join(tmpdir(), `Summer-v${info.version}.dmg`);
225
- const stagedApp = `${destApp}.new`;
226
- const releaseSignals = cleanupOnSignal([dmgPath, stagedApp]);
227
- console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
228
- try {
229
- const { sha256 } = await downloadFile(dmgUrl, dmgPath);
230
- verifyChecksum(sha256, info.dmg_sha256, "DMG");
231
- }
232
- catch (err) {
233
- releaseSignals();
234
- try {
235
- execSync(`rm -f "${dmgPath}"`, { stdio: "ignore" });
236
- }
237
- catch { /* best effort */ }
238
- console.error(err instanceof Error ? err.message : String(err));
239
- process.exit(1);
240
- }
241
- console.log("Mounting DMG...");
242
- const mountOutput = execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -noautoopen`, {
243
- encoding: "utf-8",
244
- });
245
- const mountPoint = mountOutput
246
- .split("\n")
247
- .filter((line) => line.includes("/Volumes/"))
248
- .map((line) => line.trim().split("\t").pop()?.trim())
249
- .find(Boolean);
250
- if (!mountPoint) {
251
- console.error("Failed to find mount point. Install manually from the DMG.");
252
- process.exit(1);
253
- }
254
- try {
255
- console.log("Installing to " + destApp + "...");
256
- mkdirSync(dirname(destApp), { recursive: true });
257
- replaceMacApp(mountPoint, destApp, (command) => execSync(command, { stdio: "ignore" }));
258
- console.log("Done!");
259
- }
260
- catch (err) {
261
- console.error(`Install failed: ${err instanceof Error ? err.message : String(err)}\n` +
262
- (exists ? `The existing ${destApp} was left in place.` : "Nothing was installed."));
263
- process.exitCode = 1;
264
- return;
265
- }
266
- finally {
267
- execSync(`hdiutil detach "${mountPoint}" -quiet`, { stdio: "ignore" });
268
- try {
269
- execSync(`rm "${dmgPath}"`, { stdio: "ignore" });
270
- }
271
- catch { }
272
- releaseSignals();
273
- }
274
- await recordEngineInstall(destBinary, info.version);
275
- console.log(`\nSummer Engine v${info.version} installed to ${destApp}`);
276
- console.log(FIRST_START_NOTE);
277
- console.log("\nNext steps:");
278
- console.log(" summer login # Sign in to your account");
279
- console.log(" summer run # Launch the engine");
280
- }
281
- async function installWindows(releases, customPath) {
282
- const info = releases.latest.windows;
283
- if (!info) {
284
- console.error("No Windows release found.");
285
- process.exit(1);
286
- }
287
- const exeUrl = requireDownloadUrl(info.url, "Windows installer");
288
- console.log(`Latest version: ${info.version}`);
289
- const exePath = join(tmpdir(), `Summer-v${info.version}.exe`);
290
- console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
291
- try {
292
- const { sha256 } = await downloadFile(exeUrl, exePath);
293
- verifyChecksum(sha256, info.sha256, "installer");
294
- }
295
- catch (err) {
296
- try {
297
- execSync(`del "${exePath}"`, { stdio: "ignore" });
298
- }
299
- catch { /* best effort */ }
300
- console.error(err instanceof Error ? err.message : String(err));
301
- process.exit(1);
302
- }
303
- console.log("Running installer...");
304
- try {
305
- execSync(`"${exePath}" ${windowsInstallerArgs(customPath)}`, {
306
- stdio: "inherit",
307
- });
308
- }
309
- catch {
310
- console.error("Installer failed. Try running it manually:\n " + exePath);
311
- process.exit(1);
312
- }
313
- try {
314
- execSync(`del "${exePath}"`, { stdio: "ignore" });
315
- }
316
- catch { }
317
- const installed = windowsInstalledBinary(customPath);
318
- if (installed)
319
- await recordEngineInstall(installed, info.version);
320
- console.log(`\nSummer Engine v${info.version} installed${installed ? ` to ${installed}` : "!"}`);
321
- console.log(FIRST_START_NOTE);
322
- console.log("\nNext steps:");
323
- console.log(" summer login # Sign in to your account");
324
- console.log(" summer run # Launch the engine");
325
- }
326
- // ------------------------------------------------------------------- Linux
327
- // Source resolution, release picking, and archive detection live in
328
- // core/engine-install.ts; this section is the process I/O around them.
329
- function walkFiles(dir) {
330
- const out = [];
331
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
332
- const path = join(dir, entry.name);
333
- if (entry.isDirectory())
334
- out.push(...walkFiles(path));
335
- else if (entry.isFile())
336
- out.push(path);
337
- }
338
- return out;
339
- }
340
- /** Register an existing binary the same way a download install does: the
341
- * canonical ~/.summer/engine/summer-linux-x86_64 path points at it, so
342
- * `summer run`/`summer doctor` find it even when the env var is not set. */
343
- function registerExistingLinuxBinary(sourcePath, destDir, destBinary) {
344
- const absolute = resolve(sourcePath);
345
- if (!existsSync(absolute)) {
346
- console.error(`${ENGINE_BINARY_ENV} points at ${absolute}, but nothing exists there.`);
347
- process.exit(1);
348
- }
349
- try {
350
- chmodSync(absolute, 0o755);
351
- }
352
- catch {
353
- // Best effort — a read-only mount still works if already executable.
354
- }
355
- mkdirSync(destDir, { recursive: true });
356
- if (absolute !== destBinary) {
357
- rmSync(destBinary, { force: true });
358
- symlinkSync(absolute, destBinary);
359
- }
360
- console.log(`Registered existing engine binary: ${absolute}`);
361
- console.log(` -> ${destBinary}`);
362
- }
363
- async function fetchLinuxReleaseAsset(apiUrl) {
364
- const res = await fetch(apiUrl, {
365
- headers: { Accept: "application/vnd.github+json" },
366
- signal: AbortSignal.timeout(15000),
367
- });
368
- if (!res.ok)
369
- throw new Error(`HTTP ${res.status}`);
370
- return pickLinuxReleaseAsset(await res.json());
371
- }
372
- async function installLinux(customPath) {
373
- if (process.arch !== "x64") {
374
- console.error(`Linux ${process.arch} has no published Summer Engine artifact yet (x86_64 only).\n` +
375
- `Build from source, then set ${ENGINE_BINARY_ENV} to the binary and re-run \`summer install\`.`);
376
- process.exit(1);
377
- }
378
- const destDir = customPath ? resolve(customPath) : linuxEngineInstallDir();
379
- const destBinary = join(destDir, LINUX_ENGINE_BINARY_NAME);
380
- const source = resolveLinuxInstallSource();
381
- if (source.kind === "binary") {
382
- registerExistingLinuxBinary(source.path, destDir, destBinary);
383
- await recordEngineInstall(destBinary, null);
384
- printLinuxNextSteps(destBinary);
385
- return;
386
- }
387
- let url = source.kind === "url" ? source.url : "";
388
- let sha256Url;
389
- let versionLabel = "custom";
390
- if (source.kind === "release") {
391
- console.log("Fetching latest Linux release info...");
392
- try {
393
- const asset = await fetchLinuxReleaseAsset(source.apiUrl);
394
- url = asset.url;
395
- sha256Url = asset.sha256Url;
396
- versionLabel = asset.version;
397
- }
398
- catch (err) {
399
- console.error(`Could not resolve a Linux release artifact: ${err instanceof Error ? err.message : String(err)}`);
400
- process.exit(1);
401
- }
402
- console.log(`Latest Linux release: ${versionLabel}`);
403
- }
404
- const kind = linuxArchiveKind(url);
405
- const downloadPath = join(tmpdir(), `summer-engine-${Date.now()}.${kind === "binary" ? "bin" : kind === "zip" ? "zip" : "tar.gz"}`);
406
- console.log(`Downloading ${url} ...`);
407
- try {
408
- const { sha256 } = await downloadFile(url, downloadPath);
409
- if (sha256Url) {
410
- const shaRes = await fetch(sha256Url, { signal: AbortSignal.timeout(15000) });
411
- if (shaRes.ok) {
412
- const expected = (await shaRes.text()).trim().split(/\s+/, 1)[0];
413
- verifyChecksum(sha256, expected, "Linux artifact");
414
- }
415
- }
416
- mkdirSync(destDir, { recursive: true });
417
- let extractedBinary = downloadPath;
418
- let extractDir = null;
419
- if (kind !== "binary") {
420
- extractDir = join(tmpdir(), `summer-engine-extract-${Date.now()}`);
421
- mkdirSync(extractDir, { recursive: true });
422
- if (kind === "tar.gz") {
423
- execSync(`tar -xzf "${downloadPath}" -C "${extractDir}"`, { stdio: "ignore" });
424
- }
425
- else {
426
- execSync(`unzip -oq "${downloadPath}" -d "${extractDir}"`, { stdio: "ignore" });
427
- }
428
- const found = findExtractedEngineBinary(walkFiles(extractDir));
429
- if (!found) {
430
- throw new Error(`The downloaded archive contains no engine binary (expected ${LINUX_ENGINE_BINARY_NAME}).`);
431
- }
432
- extractedBinary = found;
433
- }
434
- // Replace, never edit in place: an engine reading its own binary mid-write
435
- // is worse than a moment with no file. rename first (same tmpfs), copy as
436
- // the cross-device fallback.
437
- rmSync(destBinary, { force: true });
438
- try {
439
- renameSync(extractedBinary, destBinary);
440
- }
441
- catch {
442
- execSync(`cp "${extractedBinary}" "${destBinary}"`, { stdio: "ignore" });
443
- }
444
- chmodSync(destBinary, 0o755);
445
- if (extractDir)
446
- rmSync(extractDir, { recursive: true, force: true });
447
- }
448
- catch (err) {
449
- console.error(err instanceof Error ? err.message : String(err));
450
- process.exit(1);
451
- }
452
- finally {
453
- rmSync(downloadPath, { force: true });
454
- }
455
- await recordEngineInstall(destBinary, versionLabel === "custom" ? null : versionLabel);
456
- console.log(`\nSummer Engine (${versionLabel}) installed to ${destBinary}`);
457
- console.log(FIRST_START_NOTE);
458
- printLinuxNextSteps(destBinary);
459
- }
460
- function printLinuxNextSteps(binary) {
461
- console.log("\nNext steps:");
462
- console.log(" summer doctor # Verify the setup");
463
- console.log(` ${binary} --headless --editor --path <project> # Headless engine`);
464
- console.log(" summer login # Optional: only gateway features (asset search/generation) need it");
465
- }
466
- export async function downloadFile(url, dest) {
467
- const res = await fetch(url);
468
- if (!res.ok || !res.body) {
469
- throw new Error(`Download failed: HTTP ${res.status}`);
470
- }
471
- const dir = join(dest, "..");
472
- if (!existsSync(dir))
473
- mkdirSync(dir, { recursive: true });
474
- const fileStream = createWriteStream(dest);
475
- const readable = Readable.fromWeb(res.body);
476
- // Hash while streaming (the 'data' listener and the pipe both see every chunk
477
- // in flowing mode), so we get integrity for free without a second read pass.
478
- const hash = createHash("sha256");
479
- let downloaded = 0;
480
- const total = parseInt(res.headers.get("content-length") || "0", 10);
481
- const isTTY = Boolean(process.stdout.isTTY);
482
- const report = createProgressReporter(isTTY, (text) => process.stdout.write(text));
483
- readable.on("data", (chunk) => {
484
- hash.update(chunk);
485
- downloaded += chunk.length;
486
- report(downloaded, total);
487
- });
488
- await pipeline(readable, fileStream);
489
- if (isTTY)
490
- process.stdout.write("\n");
491
- return { sha256: hash.digest("hex") };
492
- }
@@ -15,7 +15,7 @@ const POLL_TIMEOUT_MS = 900000;
15
15
  const HEARTBEAT_MS = 30000;
16
16
  export const loginCommand = new Command("login")
17
17
  .description("Sign in to Summer in your browser: your Summer account, then the store, publishing and Grow tools in the same browser")
18
- .option("--store", "Only the store sign-in (summer_publish_build, the store, publishing, board and Grow tools); plain summer login does it too")
18
+ .option("--store", "Only the store sign-in (uploads with store.upload_file, item sync with economy.sync_items, the store, publishing, board and Grow tools); plain summer login does it too")
19
19
  .option("--no-store", "Only the Summer account sign-in; skip the store sign-in")
20
20
  .option("--force", "Force re-authentication even if already logged in")
21
21
  .action(async (opts) => {
@@ -39,7 +39,6 @@ export interface OpsCliDeps {
39
39
  files?: FileSaver;
40
40
  }
41
41
  export declare const defaultOpsCliDeps: OpsCliDeps;
42
- /** The answer for a person; `out` is the --out to keep in the printed wait command. */
43
42
  export declare function formatAnswer(answer: Answer, out?: string): string;
44
43
  /** Run one operation from the command line and print its answer; returns the exit code. */
45
44
  export declare function runOpFromCli(op: string, argv: readonly string[], deps?: OpsCliDeps): Promise<number>;
@@ -61,4 +60,21 @@ export declare function searchFromCli(query: string, options: {
61
60
  export declare function describeFromCli(op: string, deps?: OpsCliDeps, options?: {
62
61
  json?: boolean;
63
62
  }): Promise<number>;
63
+ /** `summer <area> --help`: the area's actions, from this package and the shipped catalog (no network). */
64
+ export declare function areaHelp(area: string): string;
65
+ type WireOperation = {
66
+ name: string;
67
+ title?: string;
68
+ summary?: string;
69
+ effect?: string;
70
+ approval?: string;
71
+ auth?: string;
72
+ input?: JsonSchema & {
73
+ required?: string[];
74
+ };
75
+ };
76
+ /** One operation's help: its command, what it does, who approves, the sign-in and each field as a flag. */
77
+ export declare function operationHelp(operation: WireOperation): string;
78
+ /** `summer <area> <action> --help`: from this package or the shipped catalog; the server only for an operation newer than both. */
79
+ export declare function operationHelpFromCli(op: string, deps?: OpsCliDeps): Promise<number>;
64
80
  export declare function opsCliCommands(): Command[];