summer-engine 3.2.1 → 3.4.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 (97) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/CHANGELOG.md +61 -0
  7. package/README.md +7 -4
  8. package/assets/api-docs.json.gz +0 -0
  9. package/dist/cli/commands/install.d.ts +10 -0
  10. package/dist/cli/commands/install.js +55 -10
  11. package/dist/cli/commands/login.js +12 -0
  12. package/dist/cli/commands/publish.js +2 -1
  13. package/dist/cli/commands/tool.d.ts +7 -0
  14. package/dist/cli/commands/tool.js +36 -1
  15. package/dist/core/auth.js +3 -0
  16. package/dist/core/capabilities/audit/args.d.ts +14 -14
  17. package/dist/core/capabilities/capture-gameplay.d.ts +69 -0
  18. package/dist/core/capabilities/capture-gameplay.js +283 -0
  19. package/dist/core/capabilities/creator.js +1 -1
  20. package/dist/core/capabilities/doctor.d.ts +9 -1
  21. package/dist/core/capabilities/doctor.js +47 -4
  22. package/dist/core/capabilities/engine-run.d.ts +12 -0
  23. package/dist/core/capabilities/engine-run.js +61 -0
  24. package/dist/core/capabilities/export-engine-version.d.ts +15 -0
  25. package/dist/core/capabilities/export-engine-version.js +24 -0
  26. package/dist/core/capabilities/export-game.d.ts +107 -0
  27. package/dist/core/capabilities/export-game.js +445 -0
  28. package/dist/core/capabilities/export-presets.d.ts +50 -0
  29. package/dist/core/capabilities/export-presets.js +177 -0
  30. package/dist/core/capabilities/export-templates.d.ts +118 -0
  31. package/dist/core/capabilities/export-templates.js +291 -0
  32. package/dist/core/capabilities/image-generation.d.ts +9 -0
  33. package/dist/core/capabilities/image-generation.js +19 -1
  34. package/dist/core/capabilities/publish-build.d.ts +77 -0
  35. package/dist/core/capabilities/publish-build.js +500 -0
  36. package/dist/core/capabilities/publish-download.d.ts +37 -0
  37. package/dist/core/capabilities/publish-download.js +168 -0
  38. package/dist/core/capabilities/runtime-control.d.ts +10 -10
  39. package/dist/core/capabilities/summer-bundle.d.ts +49 -0
  40. package/dist/core/capabilities/summer-bundle.js +156 -0
  41. package/dist/core/capabilities/tool-dispatch.js +43 -1
  42. package/dist/core/capabilities/ui-control.d.ts +2 -2
  43. package/dist/core/config.js +2 -2
  44. package/dist/core/engine-install.d.ts +16 -0
  45. package/dist/core/engine-install.js +32 -2
  46. package/dist/core/hosted-mcp.d.ts +115 -0
  47. package/dist/core/hosted-mcp.js +164 -0
  48. package/dist/core/launch-posture.d.ts +3 -0
  49. package/dist/core/launch-posture.js +15 -2
  50. package/dist/core/library-read.d.ts +2 -2
  51. package/dist/core/oauth.d.ts +78 -0
  52. package/dist/core/oauth.js +309 -0
  53. package/dist/core/util/zip-write.d.ts +18 -0
  54. package/dist/core/util/zip-write.js +112 -0
  55. package/dist/core/util/zip.d.ts +22 -0
  56. package/dist/core/util/zip.js +140 -0
  57. package/dist/mcp/hosted-mount.d.ts +50 -0
  58. package/dist/mcp/hosted-mount.js +200 -0
  59. package/dist/mcp/server.d.ts +2 -1
  60. package/dist/mcp/server.js +14 -6
  61. package/dist/mcp/tools/creator-tools.js +64 -1
  62. package/dist/mcp/tools/generate-tools.js +12 -2
  63. package/gemini-extension.json +3 -1
  64. package/library/references/mcp-tools-reference/mcp-tools-reference.md +7 -3
  65. package/library/skills/README.md +6 -4
  66. package/library/skills/bounded-engine-runs/SKILL.md +96 -0
  67. package/library/skills/bounded-engine-runs/resource.yaml +29 -0
  68. package/library/skills/compatibility-renderer-traps/SKILL.md +78 -0
  69. package/library/skills/compatibility-renderer-traps/resource.yaml +28 -0
  70. package/library/skills/concept-art/SKILL.md +2 -2
  71. package/library/skills/export-and-ship/SKILL.md +4 -0
  72. package/library/skills/headless-scripting/resource.yaml +1 -0
  73. package/library/skills/in-game-purchases/SKILL.md +123 -118
  74. package/library/skills/in-game-purchases/resource.yaml +2 -2
  75. package/library/skills/multiplayer-publish/SKILL.md +5 -1
  76. package/library/skills/navigate-summer/SKILL.md +1 -1
  77. package/library/skills/web-compatibility-look/resource.yaml +1 -0
  78. package/library/tools/capture-gameplay/resource.yaml +73 -0
  79. package/library/tools/creator-publish/resource.yaml +4 -3
  80. package/library/tools/creator-releases/resource.yaml +1 -1
  81. package/library/tools/export-game/resource.yaml +72 -0
  82. package/library/tools/export-templates/resource.yaml +63 -0
  83. package/library/tools/generate-image/resource.yaml +15 -1
  84. package/library/tools/open/resource.yaml +1 -1
  85. package/library/tools/publish-build/resource.yaml +78 -0
  86. package/package.json +1 -1
  87. package/registry/generated/aliases.json +4 -0
  88. package/registry/generated/counts.json +3 -3
  89. package/registry/generated/gemini-extension.json +3 -1
  90. package/registry/generated/index.json +241 -19
  91. package/registry/generated/marketplace.claude.json +2 -2
  92. package/registry/generated/plugin.claude.json +4 -2
  93. package/registry/generated/plugin.codex.json +5 -3
  94. package/registry/generated/plugin.cursor.json +4 -2
  95. package/registry/generated/plugin.factory.json +4 -2
  96. package/registry/generated/skills-index.md +6 -4
  97. package/registry/generated/skills-registry.json +28 -1
@@ -0,0 +1,177 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { BuildToolError } from "./summer-bundle.js";
5
+ /**
6
+ * Store targets for summer_export_game, and the export presets that make them.
7
+ *
8
+ * The Summer Games store has two intakes (summer-platform):
9
+ * - build-publications takes ONE summer.bundle.v1 .zip per Build. Summer runs
10
+ * its client.pck on its own client templates (the Summer Games iOS and
11
+ * Android apps, the Summer desktop app on macOS and Windows), so the creator
12
+ * needs no export template and signs nothing. A target only picks the
13
+ * platforms the bundle declares and the texture formats the pack carries
14
+ * (the summer.games preset option platforms/<id>).
15
+ * - store versions take a web build (a static HTML5 .zip played in an iframe)
16
+ * and native desktop downloads (macos-universal, windows-x64). These are
17
+ * ordinary exports and need the matching export template.
18
+ *
19
+ * Presets are kept in export_presets.cfg under fixed names, so the creator
20
+ * sees them in the Export dialog: "summer.games ios", "summer.games
21
+ * ios+android", "Summer download Web", ... Only the options listed here are
22
+ * ever rewritten; every other preset and setting stays byte-for-byte. The
23
+ * engine adds its default "summer.games" preset only while the file has no
24
+ * summer.games preset, so the default is written alongside the first named
25
+ * one.
26
+ */
27
+ export const STORE_TARGETS = ["macos", "windows", "ios", "android", "web"];
28
+ /** Targets a summer.games bundle can declare (the engine's CLIENT_PLATFORMS). */
29
+ export const BUNDLE_TARGETS = ["macos", "windows", "ios", "android"];
30
+ /** Targets the store takes as a download or web build (store versions). */
31
+ export const DOWNLOAD_TARGETS = ["macos", "windows", "web"];
32
+ /** Engine summer.games platform and default preset name (summer_games_export_platform.h PLATFORM_NAME). */
33
+ export const SUMMER_GAMES_PLATFORM = "summer.games";
34
+ export const SUMMER_GAMES_PRESET = "summer.games";
35
+ /** summer_games_export_platform.h CLIENT_FEATURE; create_preset sets it, the file must keep it. */
36
+ const SUMMER_CLIENT_FEATURE = "summer_client";
37
+ const PRESETS_FILE = "export_presets.cfg";
38
+ /** Canonical order, without repeats. */
39
+ export function normalizeTargets(targets) {
40
+ const unknown = targets.filter((target) => !STORE_TARGETS.includes(target));
41
+ if (unknown.length) {
42
+ throw new BuildToolError("target_unknown", `Unknown export target ${unknown.join(", ")}.`, `Recovery: use targets from ${STORE_TARGETS.join(", ")}.`);
43
+ }
44
+ const chosen = STORE_TARGETS.filter((target) => targets.includes(target));
45
+ if (!chosen.length) {
46
+ throw new BuildToolError("target_unknown", "No export target was given.", `Recovery: pass targets from ${STORE_TARGETS.join(", ")}.`);
47
+ }
48
+ return chosen;
49
+ }
50
+ /** Variant text for a String option. */
51
+ export function variantString(value) {
52
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
53
+ }
54
+ export function bundlePresetSpec(targets) {
55
+ return {
56
+ name: `${SUMMER_GAMES_PRESET} ${targets.join("+")}`,
57
+ platform: SUMMER_GAMES_PLATFORM,
58
+ customFeatures: SUMMER_CLIENT_FEATURE,
59
+ // Every engine platform is written, so a platform added by a newer engine
60
+ // never turns on by default in a preset made for other targets.
61
+ options: Object.fromEntries(STORE_TARGETS.map((target) => [`platforms/${target}`, String(targets.includes(target))])),
62
+ };
63
+ }
64
+ function parseSections(text) {
65
+ const preamble = [];
66
+ const sections = [];
67
+ for (const line of text.split(/\r?\n/)) {
68
+ const header = /^\[([^\]]+)\]\s*$/.exec(line);
69
+ if (header)
70
+ sections.push({ header: header[1], lines: [] });
71
+ else if (sections.length)
72
+ sections[sections.length - 1].lines.push(line);
73
+ else
74
+ preamble.push(line);
75
+ }
76
+ return { preamble, sections };
77
+ }
78
+ function stringValue(section, key) {
79
+ const prefix = `${key}=`;
80
+ for (const line of section.lines) {
81
+ if (!line.startsWith(prefix))
82
+ continue;
83
+ const match = /^"((?:[^"\\]|\\.)*)"\s*$/.exec(line.slice(prefix.length));
84
+ if (match)
85
+ return match[1].replace(/\\(["\\])/g, "$1");
86
+ }
87
+ return null;
88
+ }
89
+ function presetLines(spec) {
90
+ return [
91
+ "",
92
+ `name=${variantString(spec.name)}`,
93
+ `platform=${variantString(spec.platform)}`,
94
+ "runnable=false",
95
+ "dedicated_server=false",
96
+ ...(spec.customFeatures !== undefined ? [`custom_features=${variantString(spec.customFeatures)}`] : []),
97
+ 'export_filter="all_resources"',
98
+ 'include_filter=""',
99
+ 'exclude_filter=""',
100
+ 'export_path=""',
101
+ "",
102
+ ];
103
+ }
104
+ function optionLines(options) {
105
+ return ["", ...Object.entries(options).map(([key, value]) => `${key}=${value}`), ""];
106
+ }
107
+ /** Make sure export_presets.cfg has this preset with these option values. */
108
+ export async function ensurePreset(project, spec) {
109
+ const file = join(project, PRESETS_FILE);
110
+ const text = existsSync(file) ? await readFile(file, "utf8") : "";
111
+ const { preamble, sections } = parseSections(text);
112
+ const presets = sections
113
+ .map((section) => ({ section, index: /^preset\.(\d+)$/.exec(section.header)?.[1] }))
114
+ .filter((item) => item.index !== undefined);
115
+ const ours = presets.find(({ section }) => stringValue(section, "name") === spec.name);
116
+ if (ours) {
117
+ if (stringValue(ours.section, "platform") !== spec.platform) {
118
+ throw new BuildToolError("export_preset_conflict", `export_presets.cfg already has a preset named "${spec.name}" for another platform.`, "Recovery: rename or delete that preset in Project > Export, then export again.");
119
+ }
120
+ const optionsHeader = `preset.${ours.index}.options`;
121
+ let options = sections.find((section) => section.header === optionsHeader);
122
+ const wanted = new Map(Object.entries(spec.options));
123
+ let changed = false;
124
+ if (!options) {
125
+ options = { header: optionsHeader, lines: optionLines(spec.options) };
126
+ sections.splice(sections.indexOf(ours.section) + 1, 0, options);
127
+ changed = true;
128
+ }
129
+ else {
130
+ const seen = new Set();
131
+ options.lines = options.lines.map((line) => {
132
+ const at = line.indexOf("=");
133
+ const key = at > 0 ? line.slice(0, at) : "";
134
+ if (!wanted.has(key))
135
+ return line;
136
+ seen.add(key);
137
+ if (line.slice(at + 1).trim() === wanted.get(key))
138
+ return line;
139
+ changed = true;
140
+ return `${key}=${wanted.get(key)}`;
141
+ });
142
+ const missing = [...wanted].filter(([key]) => !seen.has(key)).map(([key, value]) => `${key}=${value}`);
143
+ if (missing.length) {
144
+ const end = options.lines.length && options.lines[options.lines.length - 1] === "" ? options.lines.length - 1 : options.lines.length;
145
+ options.lines.splice(end, 0, ...missing);
146
+ changed = true;
147
+ }
148
+ }
149
+ if (!changed)
150
+ return { name: spec.name, change: "unchanged" };
151
+ await writeFile(file, render(preamble, sections));
152
+ return { name: spec.name, change: "updated" };
153
+ }
154
+ let next = presets.reduce((max, { index }) => Math.max(max, Number(index) + 1), 0);
155
+ const added = [];
156
+ const hasSummerGames = presets.some(({ section }) => stringValue(section, "platform") === SUMMER_GAMES_PLATFORM);
157
+ if (spec.platform === SUMMER_GAMES_PLATFORM && !hasSummerGames) {
158
+ // Keep the engine's default "summer.games" preset: it leaves the editor
159
+ // once the file holds any summer.games preset. No options section, so the
160
+ // engine fills in its own defaults.
161
+ added.push({
162
+ header: `preset.${next}`,
163
+ lines: presetLines({ name: SUMMER_GAMES_PRESET, platform: SUMMER_GAMES_PLATFORM, customFeatures: SUMMER_CLIENT_FEATURE, options: {} }),
164
+ });
165
+ next += 1;
166
+ }
167
+ added.push({ header: `preset.${next}`, lines: presetLines(spec) });
168
+ added.push({ header: `preset.${next}.options`, lines: optionLines(spec.options) });
169
+ await writeFile(file, render(preamble, [...sections, ...added]));
170
+ return { name: spec.name, change: "created" };
171
+ }
172
+ function render(preamble, sections) {
173
+ const body = sections.map((section) => [`[${section.header}]`, ...section.lines].join("\n")).join("\n");
174
+ const head = preamble.join("\n");
175
+ const joined = head.trim() ? `${head.replace(/\n*$/, "\n")}\n${body}` : body;
176
+ return joined.endsWith("\n") ? joined : `${joined}\n`;
177
+ }
@@ -0,0 +1,118 @@
1
+ import { type EngineRun } from "./engine-run.js";
2
+ /**
3
+ * summer_export_templates: list, download and install Summer Engine export
4
+ * templates from Summer's CDN.
5
+ *
6
+ * The Summer Games store's own builds (summer.games bundles for iOS, Android,
7
+ * macOS and Windows) need no template. Templates are for the store's web
8
+ * build (the Summer WebGPU/JSPI Forward+ template, web_summer_jspi_*.zip) and
9
+ * for native downloads (macos.zip, windows_*_x86_64.exe). The Mac app bundles
10
+ * only macos.zip, the Windows app only the Windows exes, both bundle the web
11
+ * pair when packaged with it, and Linux editors bundle nothing; the editor's
12
+ * own downloader reads the stock Godot mirrors, which never carry Summer's
13
+ * binaries. So this tool fetches them from
14
+ *
15
+ * <base>/<summerVersion>/manifest.json (schema summer.export-templates.v1)
16
+ *
17
+ * verifies each file's sha256 and installs it where the engine looks:
18
+ * <user data>/export_templates/<FULL_CONFIG>/ (Godot's folder; Summer has not
19
+ * renamed it), FULL_CONFIG being the Godot version the engine prints with
20
+ * --version (4.7.2.stable, plus .mono for the .NET editor).
21
+ */
22
+ export declare const TEMPLATES_URL_ENV = "SUMMER_TEMPLATES_URL";
23
+ export declare const DEFAULT_TEMPLATES_URL = "https://downloads.summer.games/engine-templates";
24
+ export declare const TEMPLATES_MANIFEST_SCHEMA = "summer.export-templates.v1";
25
+ export declare const TEMPLATE_PLATFORMS: readonly ["web", "macos", "windows", "linux", "ios", "android"];
26
+ export type TemplatePlatform = (typeof TEMPLATE_PLATFORMS)[number];
27
+ export interface TemplateEntry {
28
+ platform: TemplatePlatform;
29
+ /** File name the engine looks for (macos.zip, web_summer_jspi_release.zip, ...). */
30
+ file: string;
31
+ /** Path of the file relative to the manifest. */
32
+ path: string;
33
+ sha256: string;
34
+ size: number;
35
+ debug: boolean;
36
+ /** true: only the .mono folder; false: only the standard folder; null: both. */
37
+ mono: boolean | null;
38
+ }
39
+ export interface TemplatesManifest {
40
+ schema: typeof TEMPLATES_MANIFEST_SCHEMA;
41
+ summerVersion: string;
42
+ /** Godot FULL_CONFIG without .mono, e.g. 4.7.2.stable: the template folder name. */
43
+ godotVersion: string;
44
+ templates: TemplateEntry[];
45
+ }
46
+ export interface EngineTemplateInfo {
47
+ engine: string;
48
+ /** Template folder name: Godot FULL_CONFIG, e.g. 4.7.2.stable.mono. */
49
+ folder: string;
50
+ mono: boolean;
51
+ summerVersion: string | null;
52
+ /** Folders the engine reads, first match wins: inside the app, then the user folder. */
53
+ searchDirs: string[];
54
+ /** Where downloads go. */
55
+ userDir: string;
56
+ }
57
+ export interface TemplatesDependencies {
58
+ findBinary: () => string | null;
59
+ run: (binary: string, args: string[], timeoutMs: number) => Promise<EngineRun>;
60
+ fetch: typeof globalThis.fetch;
61
+ os: NodeJS.Platform;
62
+ env: NodeJS.ProcessEnv;
63
+ home: string;
64
+ }
65
+ /** Godot's user data folder + export_templates (editor_paths.cpp; get_godot_dir_name is "Godot", "godot" on Linux). */
66
+ export declare function userTemplatesRoot(os: NodeJS.Platform, env: NodeJS.ProcessEnv, home: string): string;
67
+ /** Templates inside the installed app (editor_export_platform.cpp find_export_template). */
68
+ export declare function bundledTemplatesRoot(os: NodeJS.Platform, engine: string): string | null;
69
+ /** FULL_CONFIG from `--version` output ("4.7.2.stable.mono.custom_build.abc" -> "4.7.2.stable.mono"). */
70
+ export declare function parseTemplateFolder(output: string): string | null;
71
+ export declare function engineTemplateInfo(overrides?: Partial<TemplatesDependencies>, summerVersion?: string): Promise<EngineTemplateInfo>;
72
+ /** Where the engine would find this template file, or null. */
73
+ export declare function findInstalledTemplate(info: EngineTemplateInfo, file: string): string | null;
74
+ /** Template file the engine needs for a download export (export plugins' template names). */
75
+ export declare function templateFileFor(target: "web" | "macos" | "windows", debug: boolean): string;
76
+ export declare function templatesBaseUrl(env?: NodeJS.ProcessEnv): string;
77
+ export declare function fetchTemplatesManifest(summerVersion: string, overrides?: Partial<TemplatesDependencies>): Promise<{
78
+ manifest: TemplatesManifest;
79
+ url: string;
80
+ }>;
81
+ export interface TemplatesInput {
82
+ action: "list" | "install";
83
+ /** Template platforms to install (default: web). */
84
+ platforms?: string[];
85
+ /** Also install debug templates. */
86
+ includeDebug?: boolean;
87
+ /** Summer version of the installed engine, when the tool cannot read it (Windows, Linux). */
88
+ summerVersion?: string;
89
+ }
90
+ export declare function exportTemplates(input: TemplatesInput, overrides?: Partial<TemplatesDependencies>): Promise<{
91
+ installed: {
92
+ file: string;
93
+ dir: string;
94
+ }[];
95
+ available: (TemplateEntry & {
96
+ installed: boolean;
97
+ })[] | null;
98
+ availability: string;
99
+ note: string;
100
+ engine: string;
101
+ folder: string;
102
+ summerVersion: string | null;
103
+ searchDirs: string[];
104
+ ok: true;
105
+ } | {
106
+ manifest: string;
107
+ templates: {
108
+ file: string;
109
+ path: string;
110
+ status: "installed" | "already_installed";
111
+ }[];
112
+ next: string;
113
+ engine: string;
114
+ folder: string;
115
+ summerVersion: string | null;
116
+ searchDirs: string[];
117
+ ok: true;
118
+ }>;
@@ -0,0 +1,291 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readdirSync, readFileSync, createWriteStream } from "node:fs";
3
+ import { mkdir, rename, rm } from "node:fs/promises";
4
+ import { homedir, platform as osPlatform } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
8
+ import { findEngineBinary } from "../engine-install.js";
9
+ import { runEngine } from "./engine-run.js";
10
+ import { hashFile, BuildToolError } from "./summer-bundle.js";
11
+ /**
12
+ * summer_export_templates: list, download and install Summer Engine export
13
+ * templates from Summer's CDN.
14
+ *
15
+ * The Summer Games store's own builds (summer.games bundles for iOS, Android,
16
+ * macOS and Windows) need no template. Templates are for the store's web
17
+ * build (the Summer WebGPU/JSPI Forward+ template, web_summer_jspi_*.zip) and
18
+ * for native downloads (macos.zip, windows_*_x86_64.exe). The Mac app bundles
19
+ * only macos.zip, the Windows app only the Windows exes, both bundle the web
20
+ * pair when packaged with it, and Linux editors bundle nothing; the editor's
21
+ * own downloader reads the stock Godot mirrors, which never carry Summer's
22
+ * binaries. So this tool fetches them from
23
+ *
24
+ * <base>/<summerVersion>/manifest.json (schema summer.export-templates.v1)
25
+ *
26
+ * verifies each file's sha256 and installs it where the engine looks:
27
+ * <user data>/export_templates/<FULL_CONFIG>/ (Godot's folder; Summer has not
28
+ * renamed it), FULL_CONFIG being the Godot version the engine prints with
29
+ * --version (4.7.2.stable, plus .mono for the .NET editor).
30
+ */
31
+ export const TEMPLATES_URL_ENV = "SUMMER_TEMPLATES_URL";
32
+ export const DEFAULT_TEMPLATES_URL = "https://downloads.summer.games/engine-templates";
33
+ export const TEMPLATES_MANIFEST_SCHEMA = "summer.export-templates.v1";
34
+ export const TEMPLATE_PLATFORMS = ["web", "macos", "windows", "linux", "ios", "android"];
35
+ const SHA256 = /^sha256:[0-9a-f]{64}$/;
36
+ const SAFE_FILE = /^[A-Za-z0-9._-]+$/;
37
+ const VERSION_TIMEOUT_MS = 20_000;
38
+ const defaultDependencies = () => ({
39
+ findBinary: () => findEngineBinary(osPlatform()),
40
+ run: runEngine,
41
+ fetch: (input, init) => globalThis.fetch(input, init),
42
+ os: osPlatform(),
43
+ env: process.env,
44
+ home: homedir(),
45
+ });
46
+ /** Godot's user data folder + export_templates (editor_paths.cpp; get_godot_dir_name is "Godot", "godot" on Linux). */
47
+ export function userTemplatesRoot(os, env, home) {
48
+ if (os === "darwin")
49
+ return join(home, "Library", "Application Support", "Godot", "export_templates");
50
+ if (os === "win32")
51
+ return join(env.APPDATA || join(home, "AppData", "Roaming"), "Godot", "export_templates");
52
+ return join(env.XDG_DATA_HOME || join(home, ".local", "share"), "godot", "export_templates");
53
+ }
54
+ /** Templates inside the installed app (editor_export_platform.cpp find_export_template). */
55
+ export function bundledTemplatesRoot(os, engine) {
56
+ if (os === "darwin")
57
+ return join(dirname(dirname(engine)), "Resources", "export_templates");
58
+ if (os === "win32")
59
+ return join(dirname(engine), "export_templates");
60
+ return null;
61
+ }
62
+ /** FULL_CONFIG from `--version` output ("4.7.2.stable.mono.custom_build.abc" -> "4.7.2.stable.mono"). */
63
+ export function parseTemplateFolder(output) {
64
+ const match = /(?<![\d.])(\d+\.\d+(?:\.\d+)?\.(?:stable|dev\d*|alpha\d*|beta\d*|rc\d*)(?:\.mono)?)\b/.exec(output);
65
+ return match ? match[1] : null;
66
+ }
67
+ /** The Mac app's CFBundleShortVersionString is the Summer version. */
68
+ function macSummerVersion(engine) {
69
+ const plist = join(dirname(dirname(engine)), "Info.plist");
70
+ if (!existsSync(plist))
71
+ return null;
72
+ const match = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(readFileSync(plist, "utf8"));
73
+ return match ? match[1].trim() : null;
74
+ }
75
+ export async function engineTemplateInfo(overrides = {}, summerVersion) {
76
+ const deps = { ...defaultDependencies(), ...overrides };
77
+ const engine = deps.findBinary();
78
+ if (!engine) {
79
+ throw new BuildToolError("engine_not_installed", "Summer Engine is not installed on this machine.", 'Recovery: run "summer install", or set SUMMER_BIN to the engine executable.');
80
+ }
81
+ let run;
82
+ try {
83
+ run = await deps.run(engine, ["--headless", "--version"], VERSION_TIMEOUT_MS);
84
+ }
85
+ catch (error) {
86
+ throw new BuildToolError("engine_launch_failed", `Summer Engine could not start: ${error instanceof Error ? error.message : String(error)}.`, 'Recovery: run "summer doctor" and check the engine install.');
87
+ }
88
+ const folder = parseTemplateFolder(run.output);
89
+ if (!folder) {
90
+ throw new BuildToolError("engine_version_unknown", "Summer Engine did not print a version this tool can read.", 'Recovery: run "summer doctor"; update Summer Engine if it is very old.', undefined, { output: run.output.trim().slice(-400) });
91
+ }
92
+ const bundled = bundledTemplatesRoot(deps.os, engine);
93
+ const userRoot = userTemplatesRoot(deps.os, deps.env, deps.home);
94
+ const version = summerVersion?.trim() || (deps.os === "darwin" ? macSummerVersion(engine) : null);
95
+ return {
96
+ engine,
97
+ folder,
98
+ mono: folder.endsWith(".mono"),
99
+ summerVersion: version,
100
+ searchDirs: [...(bundled ? [join(bundled, folder)] : []), join(userRoot, folder)],
101
+ userDir: join(userRoot, folder),
102
+ };
103
+ }
104
+ /** Where the engine would find this template file, or null. */
105
+ export function findInstalledTemplate(info, file) {
106
+ for (const dir of info.searchDirs) {
107
+ const path = join(dir, file);
108
+ if (existsSync(path))
109
+ return path;
110
+ }
111
+ return null;
112
+ }
113
+ /** Template file the engine needs for a download export (export plugins' template names). */
114
+ export function templateFileFor(target, debug) {
115
+ if (target === "web")
116
+ return `web_summer_jspi_${debug ? "debug" : "release"}.zip`;
117
+ if (target === "macos")
118
+ return "macos.zip";
119
+ return `windows_${debug ? "debug" : "release"}_x86_64.exe`;
120
+ }
121
+ export function templatesBaseUrl(env = process.env) {
122
+ return (env[TEMPLATES_URL_ENV]?.trim() || DEFAULT_TEMPLATES_URL).replace(/\/+$/, "");
123
+ }
124
+ function notPublished(summerVersion, url) {
125
+ return new BuildToolError("templates_not_published", `Summer has not published export templates for Summer ${summerVersion} (${url}).`, "Recovery: store builds for iOS, Android, macOS and Windows need no template (summer_export_game with format bundle). " +
126
+ "For web or native downloads use the Summer Engine Mac or Windows app, which bundles them, or update Summer Engine and retry.", 404);
127
+ }
128
+ function parseManifest(raw, url) {
129
+ const bad = (why) => new BuildToolError("templates_manifest_invalid", `The templates manifest at ${url} is not valid: ${why}.`, "Recovery: retry later; report it if it persists.");
130
+ const value = raw;
131
+ if (!value || value.schema !== TEMPLATES_MANIFEST_SCHEMA)
132
+ throw bad(`schema is not ${TEMPLATES_MANIFEST_SCHEMA}`);
133
+ if (typeof value.summerVersion !== "string" || typeof value.godotVersion !== "string" || !Array.isArray(value.templates)) {
134
+ throw bad("summerVersion, godotVersion or templates is missing");
135
+ }
136
+ const templates = value.templates.map((item, index) => {
137
+ if (!TEMPLATE_PLATFORMS.includes(item?.platform) ||
138
+ typeof item.file !== "string" ||
139
+ !SAFE_FILE.test(item.file) ||
140
+ typeof item.path !== "string" ||
141
+ item.path.split("/").some((part) => !part || part === "." || part === "..") ||
142
+ typeof item.sha256 !== "string" ||
143
+ !SHA256.test(item.sha256) ||
144
+ !Number.isSafeInteger(item.size) ||
145
+ item.size <= 0 ||
146
+ typeof item.debug !== "boolean" ||
147
+ !(item.mono === null || typeof item.mono === "boolean")) {
148
+ throw bad(`templates[${index}] is malformed`);
149
+ }
150
+ return { platform: item.platform, file: item.file, path: item.path, sha256: item.sha256, size: item.size, debug: item.debug, mono: item.mono };
151
+ });
152
+ return { schema: TEMPLATES_MANIFEST_SCHEMA, summerVersion: value.summerVersion, godotVersion: value.godotVersion, templates };
153
+ }
154
+ export async function fetchTemplatesManifest(summerVersion, overrides = {}) {
155
+ const deps = { ...defaultDependencies(), ...overrides };
156
+ const url = `${templatesBaseUrl(deps.env)}/${encodeURIComponent(summerVersion)}/manifest.json`;
157
+ let response;
158
+ try {
159
+ response = await deps.fetch(url, { headers: { accept: "application/json" } });
160
+ }
161
+ catch (error) {
162
+ throw new BuildToolError("templates_unreachable", `Could not reach ${url}: ${error instanceof Error ? error.message : String(error)}.`, "Recovery: check the network and retry.");
163
+ }
164
+ if (response.status === 404 || response.status === 403)
165
+ throw notPublished(summerVersion, url);
166
+ if (!response.ok) {
167
+ throw new BuildToolError("templates_unreachable", `${url} answered ${response.status}.`, "Recovery: retry later.", response.status);
168
+ }
169
+ let body;
170
+ try {
171
+ body = await response.json();
172
+ }
173
+ catch {
174
+ body = null;
175
+ }
176
+ return { manifest: parseManifest(body, url), url };
177
+ }
178
+ function servesFolder(entry, mono) {
179
+ return entry.mono === null || entry.mono === mono;
180
+ }
181
+ function wantedPlatforms(platforms) {
182
+ const list = platforms?.length ? platforms : ["web"];
183
+ const unknown = list.filter((item) => !TEMPLATE_PLATFORMS.includes(item));
184
+ if (unknown.length) {
185
+ throw new BuildToolError("template_platform_unknown", `Unknown template platform ${unknown.join(", ")}.`, `Recovery: use ${TEMPLATE_PLATFORMS.join(", ")}.`);
186
+ }
187
+ return TEMPLATE_PLATFORMS.filter((item) => list.includes(item));
188
+ }
189
+ function needVersion(info) {
190
+ if (info.summerVersion)
191
+ return info.summerVersion;
192
+ throw new BuildToolError("summer_version_unknown", "This tool cannot read the installed Summer version on this system.", "Recovery: pass summerVersion (shown in Summer Engine, Help > About, e.g. 0.7.1).");
193
+ }
194
+ async function download(deps, url, entry, target) {
195
+ let response;
196
+ try {
197
+ response = await deps.fetch(url);
198
+ }
199
+ catch (error) {
200
+ throw new BuildToolError("templates_unreachable", `Could not download ${url}: ${error instanceof Error ? error.message : String(error)}.`, "Recovery: retry.");
201
+ }
202
+ if (!response.ok || !response.body) {
203
+ throw new BuildToolError("templates_unreachable", `${url} answered ${response.status}.`, "Recovery: retry later.", response.status);
204
+ }
205
+ await mkdir(dirname(target), { recursive: true });
206
+ const part = `${target}.part`;
207
+ const hash = createHash("sha256");
208
+ let size = 0;
209
+ try {
210
+ await pipeline(Readable.fromWeb(response.body), async function* (source) {
211
+ for await (const chunk of source) {
212
+ hash.update(chunk);
213
+ size += chunk.length;
214
+ yield chunk;
215
+ }
216
+ }, createWriteStream(part));
217
+ const sha256 = `sha256:${hash.digest("hex")}`;
218
+ if (sha256 !== entry.sha256 || size !== entry.size) {
219
+ throw new BuildToolError("template_checksum_mismatch", `${entry.file} did not match the manifest (got ${sha256}, ${size} bytes).`, "Recovery: retry; nothing was installed.");
220
+ }
221
+ await rename(part, target);
222
+ }
223
+ finally {
224
+ await rm(part, { force: true });
225
+ }
226
+ }
227
+ export async function exportTemplates(input, overrides = {}) {
228
+ const deps = { ...defaultDependencies(), ...overrides };
229
+ const info = await engineTemplateInfo(deps, input.summerVersion);
230
+ const installedFiles = () => info.searchDirs.flatMap((dir) => existsSync(dir) ? readdirSync(dir).filter((name) => !name.endsWith(".part")).map((file) => ({ file, dir })) : []);
231
+ const engine = { engine: info.engine, folder: info.folder, summerVersion: info.summerVersion, searchDirs: info.searchDirs };
232
+ if (input.action === "list") {
233
+ let available = null;
234
+ let availability;
235
+ if (info.summerVersion) {
236
+ try {
237
+ const { manifest, url } = await fetchTemplatesManifest(info.summerVersion, deps);
238
+ available = manifest.templates
239
+ .filter((entry) => servesFolder(entry, info.mono))
240
+ .map((entry) => ({ ...entry, installed: findInstalledTemplate(info, entry.file) !== null }));
241
+ availability = url;
242
+ }
243
+ catch (error) {
244
+ if (!(error instanceof BuildToolError))
245
+ throw error;
246
+ availability = `${error.code}: ${error.message.replace(` ${error.recovery}`, "")}`;
247
+ }
248
+ }
249
+ else {
250
+ availability = "summer_version_unknown: pass summerVersion to see what Summer publishes.";
251
+ }
252
+ return {
253
+ ok: true,
254
+ ...engine,
255
+ installed: installedFiles(),
256
+ available,
257
+ availability,
258
+ note: "Store builds (summer_export_game, format bundle) need no template. Web and native downloads need web_summer_jspi_release.zip, macos.zip or windows_release_x86_64.exe.",
259
+ };
260
+ }
261
+ const platforms = wantedPlatforms(input.platforms);
262
+ const summerVersion = needVersion(info);
263
+ const { manifest, url } = await fetchTemplatesManifest(summerVersion, deps);
264
+ const godotFolder = info.folder.replace(/\.mono$/, "");
265
+ if (manifest.godotVersion !== godotFolder) {
266
+ throw new BuildToolError("templates_version_mismatch", `Summer ${summerVersion} templates are for Godot base ${manifest.godotVersion}, but this engine reads ${info.folder}.`, "Recovery: pass the summerVersion of the installed Summer Engine (Help > About), or update Summer Engine.");
267
+ }
268
+ const entries = manifest.templates.filter((entry) => platforms.includes(entry.platform) && (input.includeDebug || !entry.debug) && servesFolder(entry, info.mono));
269
+ const missing = platforms.filter((item) => !entries.some((entry) => entry.platform === item));
270
+ if (missing.length) {
271
+ throw new BuildToolError("template_not_published", `Summer ${summerVersion} publishes no ${missing.join(", ")} template${info.mono ? " for the .NET editor" : ""}.`, `Recovery: install from ${manifest.templates.map((entry) => entry.platform).filter((v, i, a) => a.indexOf(v) === i).join(", ") || "nothing yet"}.`);
272
+ }
273
+ const base = url.slice(0, url.lastIndexOf("/") + 1);
274
+ const results = [];
275
+ for (const entry of entries) {
276
+ const target = join(info.userDir, entry.file);
277
+ if (existsSync(target) && (await hashFile(target)).sha256 === entry.sha256) {
278
+ results.push({ file: entry.file, path: target, status: "already_installed" });
279
+ continue;
280
+ }
281
+ await download(deps, base + entry.path.split("/").map(encodeURIComponent).join("/"), entry, target);
282
+ results.push({ file: entry.file, path: target, status: "installed" });
283
+ }
284
+ return {
285
+ ok: true,
286
+ ...engine,
287
+ manifest: url,
288
+ templates: results,
289
+ next: "Export with summer_export_game (format download) for web, macos or windows.",
290
+ };
291
+ }
@@ -6,19 +6,28 @@ export declare const imageGenerationArgsSchema: z.ZodObject<{
6
6
  style: z.ZodDefault<z.ZodString>;
7
7
  referenceImageUrl: z.ZodOptional<z.ZodString>;
8
8
  removeBackground: z.ZodOptional<z.ZodBoolean>;
9
+ aspectRatio: z.ZodOptional<z.ZodEnum<["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"]>>;
10
+ width: z.ZodOptional<z.ZodNumber>;
11
+ height: z.ZodOptional<z.ZodNumber>;
9
12
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
10
13
  }, "strip", z.ZodTypeAny, {
11
14
  prompt: string;
12
15
  model: string;
13
16
  style: string;
17
+ width?: number | undefined;
18
+ height?: number | undefined;
14
19
  options?: Record<string, any> | undefined;
15
20
  referenceImageUrl?: string | undefined;
16
21
  removeBackground?: boolean | undefined;
22
+ aspectRatio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "5:4" | "4:5" | "21:9" | undefined;
17
23
  }, {
18
24
  prompt: string;
25
+ width?: number | undefined;
26
+ height?: number | undefined;
19
27
  options?: Record<string, any> | undefined;
20
28
  model?: string | undefined;
21
29
  style?: string | undefined;
22
30
  referenceImageUrl?: string | undefined;
23
31
  removeBackground?: boolean | undefined;
32
+ aspectRatio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "5:4" | "4:5" | "21:9" | undefined;
24
33
  }>;
@@ -15,8 +15,26 @@ export const imageGenerationArgsSchema = z.object({
15
15
  .optional()
16
16
  .describe("Source image URL for img2img / edit mode. The prompt describes how to transform this image."),
17
17
  removeBackground: z.boolean().optional().describe("Remove the background server-side and return an alpha PNG for sprites, icons, or isolated objects."),
18
+ aspectRatio: z
19
+ .enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"])
20
+ .optional()
21
+ .describe("Aspect ratio of the output (default 1:1). The result reports the real width and height."),
22
+ width: z
23
+ .number()
24
+ .int()
25
+ .min(64)
26
+ .max(4096)
27
+ .optional()
28
+ .describe("Smallest output width in pixels; pass with height. 1920 with height 1080 for store key art, 1080 with 1920 for a tall cover."),
29
+ height: z
30
+ .number()
31
+ .int()
32
+ .min(64)
33
+ .max(4096)
34
+ .optional()
35
+ .describe("Smallest output height in pixels; pass with width."),
18
36
  options: z
19
37
  .record(z.any())
20
38
  .optional()
21
- .describe("Provider-specific params (guidance_scale, seed, image_size, negative_prompt, etc.)"),
39
+ .describe("Extra model params (seed, negative_prompt, ...). The result lists any it did not use in ignoredOptions."),
22
40
  });