summer-engine 3.4.1 → 3.5.1
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +5 -1
- package/.codex-plugin/plugin.json +5 -1
- package/.cursor-plugin/plugin.json +5 -1
- package/.factory-plugin/plugin.json +5 -1
- package/CHANGELOG.md +31 -0
- package/README.md +5 -1
- package/dist/cli/commands/setup.js +24 -4
- package/dist/core/capabilities/capture-gameplay.d.ts +72 -7
- package/dist/core/capabilities/capture-gameplay.js +386 -25
- package/dist/core/capabilities/export-engine-version.d.ts +7 -0
- package/dist/core/capabilities/export-engine-version.js +9 -0
- package/dist/core/capabilities/export-game.d.ts +4 -0
- package/dist/core/capabilities/export-game.js +50 -7
- package/dist/core/capabilities/image-generation.d.ts +12 -2
- package/dist/core/capabilities/image-generation.js +31 -2
- package/dist/core/capabilities/publish-build.d.ts +4 -0
- package/dist/core/capabilities/publish-build.js +84 -37
- package/dist/core/capabilities/runtime-control.d.ts +2 -2
- package/dist/core/capabilities/summer-bundle.d.ts +9 -0
- package/dist/core/capabilities/summer-bundle.js +19 -0
- package/dist/core/capabilities/tool-dispatch.js +13 -2
- package/dist/core/library-read.d.ts +7 -0
- package/dist/core/library-read.js +59 -7
- package/dist/core/library-search.d.ts +2 -0
- package/dist/installer/agent-config.d.ts +26 -1
- package/dist/installer/agent-config.js +180 -5
- package/dist/installer/agent-table.d.ts +13 -0
- package/dist/installer/agent-table.js +120 -0
- package/dist/mcp/tools/creator-tools.js +32 -3
- package/dist/mcp/tools/generate-tools.js +11 -30
- package/dist/mcp/tools/library-tools.d.ts +2 -1
- package/dist/mcp/tools/library-tools.js +1 -0
- package/gemini-extension.json +5 -1
- package/integrations/README.md +6 -0
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +1 -1
- package/library/skills/README.md +11 -1
- package/library/skills/export-and-ship/SKILL.md +1 -1
- package/library/skills/export-and-ship/resource.yaml +2 -1
- package/library/skills/grow-analytics/SKILL.md +21 -0
- package/library/skills/grow-analytics/resource.yaml +27 -0
- package/library/skills/publish-your-game/SKILL.md +21 -0
- package/library/skills/publish-your-game/resource.yaml +29 -0
- package/library/skills/store-art/SKILL.md +23 -0
- package/library/skills/store-art/resource.yaml +30 -0
- package/library/skills/store-listing/SKILL.md +21 -0
- package/library/skills/store-listing/resource.yaml +25 -0
- package/library/skills/summer-analytics/resource.yaml +1 -0
- package/library/tools/capture-gameplay/resource.yaml +66 -1
- package/library/tools/export-game/resource.yaml +3 -0
- package/library/tools/generate-image/resource.yaml +4 -2
- package/package.json +1 -1
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +5 -1
- package/registry/generated/index.json +153 -10
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude.json +5 -1
- package/registry/generated/plugin.codex.json +5 -1
- package/registry/generated/plugin.cursor.json +5 -1
- package/registry/generated/plugin.factory.json +5 -1
- package/registry/generated/skills-index.md +11 -1
- package/registry/generated/skills-registry.json +52 -0
- package/registry/schemas/skill.schema.json +5 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
/** Image generation can take over a minute on the larger models; wait this long for the server. */
|
|
3
|
+
export declare const IMAGE_GENERATION_TIMEOUT_MS = 180000;
|
|
2
4
|
/** Shared MCP/CLI image request contract; optional flags preserve omission. */
|
|
3
5
|
export declare const imageGenerationArgsSchema: z.ZodObject<{
|
|
4
6
|
prompt: z.ZodString;
|
|
5
|
-
model: z.
|
|
7
|
+
model: z.ZodOptional<z.ZodString>;
|
|
6
8
|
style: z.ZodDefault<z.ZodString>;
|
|
7
9
|
referenceImageUrl: z.ZodOptional<z.ZodString>;
|
|
8
10
|
removeBackground: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -10,13 +12,15 @@ export declare const imageGenerationArgsSchema: z.ZodObject<{
|
|
|
10
12
|
width: z.ZodOptional<z.ZodNumber>;
|
|
11
13
|
height: z.ZodOptional<z.ZodNumber>;
|
|
12
14
|
options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
15
|
+
out: z.ZodOptional<z.ZodString>;
|
|
13
16
|
}, "strip", z.ZodTypeAny, {
|
|
14
17
|
prompt: string;
|
|
15
|
-
model: string;
|
|
16
18
|
style: string;
|
|
17
19
|
width?: number | undefined;
|
|
18
20
|
height?: number | undefined;
|
|
19
21
|
options?: Record<string, any> | undefined;
|
|
22
|
+
out?: string | undefined;
|
|
23
|
+
model?: string | undefined;
|
|
20
24
|
referenceImageUrl?: string | undefined;
|
|
21
25
|
removeBackground?: boolean | undefined;
|
|
22
26
|
aspectRatio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "5:4" | "4:5" | "21:9" | undefined;
|
|
@@ -25,9 +29,15 @@ export declare const imageGenerationArgsSchema: z.ZodObject<{
|
|
|
25
29
|
width?: number | undefined;
|
|
26
30
|
height?: number | undefined;
|
|
27
31
|
options?: Record<string, any> | undefined;
|
|
32
|
+
out?: string | undefined;
|
|
28
33
|
model?: string | undefined;
|
|
29
34
|
style?: string | undefined;
|
|
30
35
|
referenceImageUrl?: string | undefined;
|
|
31
36
|
removeBackground?: boolean | undefined;
|
|
32
37
|
aspectRatio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "5:4" | "4:5" | "21:9" | undefined;
|
|
33
38
|
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Save a generated image where the caller asked (a folder or a file path), or
|
|
41
|
+
* under TMPDIR/summer-gen. Returns the path, or null when the download failed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function saveGeneratedImage(url: string, out?: string, prefix?: string): Promise<string | null>;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, extname, join, resolve } from "node:path";
|
|
1
4
|
import { z } from "zod";
|
|
5
|
+
/** Image generation can take over a minute on the larger models; wait this long for the server. */
|
|
6
|
+
export const IMAGE_GENERATION_TIMEOUT_MS = 180_000;
|
|
7
|
+
const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp"]);
|
|
2
8
|
/** Shared MCP/CLI image request contract; optional flags preserve omission. */
|
|
3
9
|
export const imageGenerationArgsSchema = z.object({
|
|
4
10
|
prompt: z.string().describe("Description of the image to generate"),
|
|
5
11
|
model: z
|
|
6
12
|
.string()
|
|
7
|
-
.
|
|
8
|
-
.describe("Model name or full provider ID"),
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Model name or full provider ID. Omit it and the server picks one that reaches the requested size (nano-banana-2 when no size is asked)."),
|
|
9
15
|
style: z
|
|
10
16
|
.string()
|
|
11
17
|
.default("realistic")
|
|
@@ -37,4 +43,27 @@ export const imageGenerationArgsSchema = z.object({
|
|
|
37
43
|
.record(z.any())
|
|
38
44
|
.optional()
|
|
39
45
|
.describe("Extra model params (seed, negative_prompt, ...). The result lists any it did not use in ignoredOptions."),
|
|
46
|
+
out: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Where to save the image on this machine: a folder, or a file path ending in .png, .jpg or .webp. Default: <TMPDIR>/summer-gen/."),
|
|
40
50
|
});
|
|
51
|
+
/**
|
|
52
|
+
* Save a generated image where the caller asked (a folder or a file path), or
|
|
53
|
+
* under TMPDIR/summer-gen. Returns the path, or null when the download failed.
|
|
54
|
+
*/
|
|
55
|
+
export async function saveGeneratedImage(url, out, prefix = "img") {
|
|
56
|
+
try {
|
|
57
|
+
const target = out?.trim() ? resolve(out.trim()) : join(tmpdir(), "summer-gen");
|
|
58
|
+
const file = IMAGE_EXTENSIONS.has(extname(target).toLowerCase()) ? target : join(target, `${prefix}-${Date.now()}.png`);
|
|
59
|
+
await mkdir(dirname(file), { recursive: true });
|
|
60
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(60_000) });
|
|
61
|
+
if (!res.ok)
|
|
62
|
+
return null;
|
|
63
|
+
await writeFile(file, Buffer.from(await res.arrayBuffer()));
|
|
64
|
+
return file;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -51,6 +51,10 @@ export declare function readGrants(body: Record<string, any>): Grant[];
|
|
|
51
51
|
* Upload the given parts: signed in batches, PUT from disk, re-signed when
|
|
52
52
|
* refused or about to expire. `sign` asks the store for URLs (build
|
|
53
53
|
* publications bind each to its part SHA-256; store versions to its length).
|
|
54
|
+
* Each sign asks for every part not yet uploaded, including the parts other
|
|
55
|
+
* workers hold, so one sign usually covers them all. A part fails to sign only
|
|
56
|
+
* when the store was asked for it and did not sign it; the first fatal error
|
|
57
|
+
* stops the other workers and aborts their uploads.
|
|
54
58
|
*/
|
|
55
59
|
export declare function uploadParts(sign: (partNumbers: number[]) => Promise<Grant[]>, file: string, sizeBytes: number, plan: {
|
|
56
60
|
partSizeBytes: number;
|
|
@@ -8,7 +8,7 @@ import { appendStoreJsonLine } from "../store.js";
|
|
|
8
8
|
import { readJsonResponse } from "../util/http.js";
|
|
9
9
|
import { readLastExport } from "./export-game.js";
|
|
10
10
|
import { publishDownload } from "./publish-download.js";
|
|
11
|
-
import { BuildToolError, readSummerBundle } from "./summer-bundle.js";
|
|
11
|
+
import { BuildToolError, declarationMismatch, readSummerBundle } from "./summer-bundle.js";
|
|
12
12
|
/**
|
|
13
13
|
* summer_publish_build: upload a summer.games export to the creator's game
|
|
14
14
|
* through the same creator store routes Studio's export upload uses
|
|
@@ -47,7 +47,7 @@ const INTAKE_FAILURES = {
|
|
|
47
47
|
game_identity_mismatch: "This export belongs to another game. Upload it to its original game, or make a portable export for this game.",
|
|
48
48
|
bundle_invalid: "Summer could not read this export. Export the game again and upload the new file.",
|
|
49
49
|
declaration_mismatch: "The export's game or settings do not match this upload. Use the original game or export a new version with the intended settings.",
|
|
50
|
-
template_set_unavailable: "Summer Games cannot run this Summer version yet.
|
|
50
|
+
template_set_unavailable: "Summer Games cannot run this Summer version yet. If you exported with the newest Summer Engine, Summer Games may still be turning that version on: retry the same upload later, there is nothing to update. With an older engine, update Summer Engine and export again.",
|
|
51
51
|
client_version_conflict: "This version number already belongs to a different export. Use a new clientVersion.",
|
|
52
52
|
artifact_exists: "This exact export is already uploaded for this game. Export a new version and upload that.",
|
|
53
53
|
approval_denied: "Summer Games did not approve this game, so it cannot take new builds.",
|
|
@@ -185,19 +185,28 @@ async function readPart(path, partNumber, partSize, sizeBytes) {
|
|
|
185
185
|
* Upload the given parts: signed in batches, PUT from disk, re-signed when
|
|
186
186
|
* refused or about to expire. `sign` asks the store for URLs (build
|
|
187
187
|
* publications bind each to its part SHA-256; store versions to its length).
|
|
188
|
+
* Each sign asks for every part not yet uploaded, including the parts other
|
|
189
|
+
* workers hold, so one sign usually covers them all. A part fails to sign only
|
|
190
|
+
* when the store was asked for it and did not sign it; the first fatal error
|
|
191
|
+
* stops the other workers and aborts their uploads.
|
|
188
192
|
*/
|
|
189
193
|
export async function uploadParts(sign, file, sizeBytes, plan, deps, parts = Array.from({ length: plan.partCount }, (_, index) => index + 1)) {
|
|
190
194
|
const queue = [...parts];
|
|
195
|
+
const unfinished = new Set(parts);
|
|
191
196
|
const grants = new Map();
|
|
197
|
+
const stop = new AbortController();
|
|
198
|
+
let failure;
|
|
192
199
|
let signing = null;
|
|
193
200
|
const fresh = (grant) => grant !== undefined && grant.expiresAt - deps.now() > 60_000;
|
|
194
201
|
const grantFor = async (partNumber) => {
|
|
195
|
-
for (
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
for (;;) {
|
|
203
|
+
const grant = grants.get(partNumber);
|
|
204
|
+
if (fresh(grant))
|
|
205
|
+
return grant;
|
|
198
206
|
if (!signing) {
|
|
199
|
-
const
|
|
200
|
-
|
|
207
|
+
const others = [...unfinished].filter((n) => n !== partNumber && !fresh(grants.get(n)));
|
|
208
|
+
const batch = [partNumber, ...others].slice(0, MAX_SIGNED_PARTS);
|
|
209
|
+
const done = sign(batch)
|
|
201
210
|
.then((signed) => {
|
|
202
211
|
for (const grant of signed)
|
|
203
212
|
grants.set(grant.partNumber, grant);
|
|
@@ -205,43 +214,67 @@ export async function uploadParts(sign, file, sizeBytes, plan, deps, parts = Arr
|
|
|
205
214
|
.finally(() => {
|
|
206
215
|
signing = null;
|
|
207
216
|
});
|
|
217
|
+
signing = { batch, done };
|
|
218
|
+
}
|
|
219
|
+
const { batch, done } = signing;
|
|
220
|
+
await done;
|
|
221
|
+
// The store was asked for this part and did not sign it (or signed it already expiring).
|
|
222
|
+
if (batch.includes(partNumber) && !fresh(grants.get(partNumber))) {
|
|
223
|
+
throw new BuildToolError("store_sign_failed", `The store did not sign part ${partNumber}.`, "Recovery: retry; a retry continues the same upload.");
|
|
208
224
|
}
|
|
209
|
-
await signing;
|
|
210
225
|
}
|
|
211
|
-
throw new BuildToolError("store_sign_failed", `The store did not sign part ${partNumber}.`, "Recovery: retry; a retry continues the same upload.");
|
|
212
226
|
};
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
const upload = async (partNumber) => {
|
|
228
|
+
const body = await readPart(file, partNumber, plan.partSizeBytes, sizeBytes);
|
|
229
|
+
for (let attempt = 0;; attempt++) {
|
|
230
|
+
let status = null;
|
|
231
|
+
let error = null;
|
|
232
|
+
try {
|
|
217
233
|
const grant = await grantFor(partNumber);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
const response = await deps.fetch(grant.url, {
|
|
235
|
+
method: "PUT",
|
|
236
|
+
headers: grant.headers,
|
|
237
|
+
body,
|
|
238
|
+
redirect: "error",
|
|
239
|
+
signal: AbortSignal.any([stop.signal, AbortSignal.timeout(600_000)]),
|
|
240
|
+
});
|
|
241
|
+
if (response.ok)
|
|
242
|
+
return;
|
|
243
|
+
status = response.status;
|
|
244
|
+
}
|
|
245
|
+
catch (caught) {
|
|
246
|
+
error = caught;
|
|
247
|
+
}
|
|
248
|
+
if (stop.signal.aborted)
|
|
249
|
+
return;
|
|
250
|
+
// An expired or refused signature gets a new URL on the next try.
|
|
251
|
+
if (status === 400 || status === 403)
|
|
252
|
+
grants.delete(partNumber);
|
|
253
|
+
if (attempt >= PART_RETRIES) {
|
|
254
|
+
if (error instanceof BuildToolError)
|
|
255
|
+
throw error;
|
|
256
|
+
throw new BuildToolError("store_part_upload_failed", `Part ${partNumber} of ${plan.partCount} did not upload${status ? ` (${status})` : ""}.`, "Recovery: retry with the same file and clientVersion; the upload continues where it stopped.");
|
|
257
|
+
}
|
|
258
|
+
await deps.sleep(500 * 2 ** attempt);
|
|
259
|
+
if (stop.signal.aborted)
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const worker = async () => {
|
|
264
|
+
for (let partNumber = queue.shift(); partNumber !== undefined && !stop.signal.aborted; partNumber = queue.shift()) {
|
|
265
|
+
try {
|
|
266
|
+
await upload(partNumber);
|
|
267
|
+
unfinished.delete(partNumber);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
failure ??= error;
|
|
271
|
+
stop.abort();
|
|
241
272
|
}
|
|
242
273
|
}
|
|
243
274
|
};
|
|
244
275
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, queue.length) }, worker));
|
|
276
|
+
if (stop.signal.aborted)
|
|
277
|
+
throw failure;
|
|
245
278
|
}
|
|
246
279
|
export function normalizeClientVersion(value) {
|
|
247
280
|
const raw = value?.trim() ?? "";
|
|
@@ -252,6 +285,10 @@ export function normalizeClientVersion(value) {
|
|
|
252
285
|
return version;
|
|
253
286
|
}
|
|
254
287
|
function targetPlatforms(bundle) {
|
|
288
|
+
const mismatch = declarationMismatch(bundle);
|
|
289
|
+
if (mismatch) {
|
|
290
|
+
throw new BuildToolError("declaration_mismatch", `The store would refuse this upload (declaration_mismatch): ${mismatch}. Nothing was uploaded and no version was used.`, "Recovery: export again with summer_export_game alignDeclaration:true (it sets summer.build.json targetPlatforms to the targets you export), or with targets equal to what summer.build.json declares; then upload the new export.");
|
|
291
|
+
}
|
|
255
292
|
if (bundle.hosted)
|
|
256
293
|
return bundle.targetPlatforms;
|
|
257
294
|
const other = bundle.targetPlatforms.filter((platform) => !STANDALONE_TARGETS.includes(platform));
|
|
@@ -421,7 +458,17 @@ export async function publishBuild(input, overrides = {}) {
|
|
|
421
458
|
break;
|
|
422
459
|
if (publication.state === "failed" || publication.state === "cancelled") {
|
|
423
460
|
const code = typeof publication.errorCode === "string" ? publication.errorCode : "publication_failed";
|
|
424
|
-
throw new BuildToolError(code, (code === "declaration_mismatch"
|
|
461
|
+
throw new BuildToolError(code, ((code === "declaration_mismatch" || code === "template_set_unavailable") && publication.errorMessage) ||
|
|
462
|
+
INTAKE_FAILURES[code] ||
|
|
463
|
+
publication.errorMessage ||
|
|
464
|
+
"Summer could not make a build from this upload.",
|
|
465
|
+
// The store checks the declaration and the bundle before it records a
|
|
466
|
+
// client pack, so those refusals leave the version free.
|
|
467
|
+
code === "declaration_mismatch" || code === "bundle_invalid"
|
|
468
|
+
? "Recovery: fix what the message names, export again and retry; the same clientVersion still works, because a refused upload records no version."
|
|
469
|
+
: code === "template_set_unavailable"
|
|
470
|
+
? `Recovery: ${INTAKE_FAILURES.template_set_unavailable.replace(/^Summer Games cannot run this Summer version yet\. /, "")} The same clientVersion works on the retry.`
|
|
471
|
+
: "Recovery: export again, use a new clientVersion, and retry.", undefined, { publicationId });
|
|
425
472
|
}
|
|
426
473
|
if (deps.now() >= deadline) {
|
|
427
474
|
const result = {
|
|
@@ -186,8 +186,8 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
|
|
|
186
186
|
value?: string | number | boolean | undefined;
|
|
187
187
|
instance?: string | undefined;
|
|
188
188
|
name?: string | undefined;
|
|
189
|
-
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
190
189
|
space?: "local" | "global" | "both" | undefined;
|
|
190
|
+
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
191
191
|
blend?: number | undefined;
|
|
192
192
|
position?: number | undefined;
|
|
193
193
|
speed?: number | undefined;
|
|
@@ -205,8 +205,8 @@ export declare const runtimeAnimateArgsSchema: z.ZodObject<{
|
|
|
205
205
|
value?: string | number | boolean | undefined;
|
|
206
206
|
instance?: string | undefined;
|
|
207
207
|
name?: string | undefined;
|
|
208
|
-
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
209
208
|
space?: "local" | "global" | "both" | undefined;
|
|
209
|
+
cmd?: "state" | "pause" | "play" | "stop" | "seek" | "speed" | "travel" | "start" | "set_param" | "get_param" | undefined;
|
|
210
210
|
blend?: number | undefined;
|
|
211
211
|
position?: number | undefined;
|
|
212
212
|
speed?: number | undefined;
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const BUNDLE_SCHEMA = "summer.bundle.v1";
|
|
8
8
|
export declare const BUNDLE_MANIFEST = "summer-bundle.json";
|
|
9
|
+
/** The same platforms, in any order. */
|
|
10
|
+
export declare function samePlatforms(a: readonly string[], b: readonly string[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Why the store will refuse this hosted bundle's Build declaration, or null.
|
|
13
|
+
* The store requires summer.build.json's targetPlatforms to equal the
|
|
14
|
+
* platforms the client pack was exported for (declaration_mismatch), and the
|
|
15
|
+
* refusal spends the clientVersion, so this is checked before any upload.
|
|
16
|
+
*/
|
|
17
|
+
export declare function declarationMismatch(bundle: Pick<SummerBundle, "hostedBuild" | "targetPlatforms">): string | null;
|
|
9
18
|
/** A failure with a stable code, a plain message and the next step. */
|
|
10
19
|
export declare class BuildToolError extends Error {
|
|
11
20
|
readonly code: string;
|
|
@@ -12,6 +12,25 @@ export const BUNDLE_MANIFEST = "summer-bundle.json";
|
|
|
12
12
|
const BUNDLE_CLIENT = "client.pck";
|
|
13
13
|
const BUNDLE_SERVER = "server.pck";
|
|
14
14
|
const BUNDLE_BUILD = "config/summer.build.json";
|
|
15
|
+
/** The same platforms, in any order. */
|
|
16
|
+
export function samePlatforms(a, b) {
|
|
17
|
+
return a.length === b.length && [...a].sort().join(",") === [...b].sort().join(",");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Why the store will refuse this hosted bundle's Build declaration, or null.
|
|
21
|
+
* The store requires summer.build.json's targetPlatforms to equal the
|
|
22
|
+
* platforms the client pack was exported for (declaration_mismatch), and the
|
|
23
|
+
* refusal spends the clientVersion, so this is checked before any upload.
|
|
24
|
+
*/
|
|
25
|
+
export function declarationMismatch(bundle) {
|
|
26
|
+
const declared = bundle.hostedBuild?.targetPlatforms;
|
|
27
|
+
if (!Array.isArray(declared))
|
|
28
|
+
return null;
|
|
29
|
+
const platforms = declared.map(String);
|
|
30
|
+
return samePlatforms(platforms, bundle.targetPlatforms)
|
|
31
|
+
? null
|
|
32
|
+
: `the bundle targets ${[...bundle.targetPlatforms].sort().join(", ")} but its summer.build.json declares ${[...platforms].sort().join(", ")}`;
|
|
33
|
+
}
|
|
15
34
|
const SHA256 = /^sha256:[0-9a-f]{64}$/;
|
|
16
35
|
const AS_EXPORTED = "Recovery: export the game again with the summer.games preset and use the .zip exactly as Summer Engine wrote it.";
|
|
17
36
|
/** A failure with a stable code, a plain message and the next step. */
|
|
@@ -28,7 +28,7 @@ import { executeSceneMutation, occurrenceCount, readTextPayload, safeProjectPath
|
|
|
28
28
|
import { extractOpError, withOldEngineHint } from "./engine-receipt.js";
|
|
29
29
|
import { lookupApiDocs } from "./api-docs.js";
|
|
30
30
|
import { BOARD_ENDPOINT, boardArgsSchema, boardQuery } from "./board.js";
|
|
31
|
-
import { imageGenerationArgsSchema } from "./image-generation.js";
|
|
31
|
+
import { IMAGE_GENERATION_TIMEOUT_MS, imageGenerationArgsSchema, saveGeneratedImage } from "./image-generation.js";
|
|
32
32
|
import { buildMotionRequestBody, motionGenerationArgsSchema } from "./motion-generation.js";
|
|
33
33
|
import { ImportHdriError, importHdriArgsSchema, importPolyHavenHdri } from "./hdri-import.js";
|
|
34
34
|
import { FABRICATE_FALLBACK, buildFabricateMeshOp, fabricateArgsSchema } from "./fabricate-mesh.js";
|
|
@@ -618,6 +618,7 @@ export const TOOL_DISPATCH = [
|
|
|
618
618
|
targets: Array.isArray(args.targets) ? args.targets.map(String) : undefined,
|
|
619
619
|
format: optStr(args, "format"),
|
|
620
620
|
debug: args.debug === true,
|
|
621
|
+
alignDeclaration: args.alignDeclaration === true,
|
|
621
622
|
...(typeof args.timeoutSeconds === "number" ? { timeoutMs: args.timeoutSeconds * 1000 } : {}),
|
|
622
623
|
})),
|
|
623
624
|
entry("summer_export_templates", "List or install Summer export templates from the CDN (web, macos, windows)", false, (args) => {
|
|
@@ -639,6 +640,8 @@ export const TOOL_DISPATCH = [
|
|
|
639
640
|
waitSeconds: optNumberOrUndefined(args, "waitSeconds"),
|
|
640
641
|
intervalSeconds: optNumberOrUndefined(args, "intervalSeconds"),
|
|
641
642
|
out: optStr(args, "out"),
|
|
643
|
+
args: args.args,
|
|
644
|
+
steps: args.steps,
|
|
642
645
|
})),
|
|
643
646
|
entry("summer_publish_build", "Upload a summer.games export to your store game (confirm-gated)", false, (args) => publishBuild({
|
|
644
647
|
gameId: optStr(args, "gameId"),
|
|
@@ -896,7 +899,15 @@ export const TOOL_DISPATCH = [
|
|
|
896
899
|
const parsed = parseToolArgs(boardArgsSchema, args, "get-board");
|
|
897
900
|
return gatewayGet(BOARD_ENDPOINT, boardQuery({ ...parsed, pictures: parsed.pictures ?? false }), 45_000);
|
|
898
901
|
}),
|
|
899
|
-
entry("summer_generate_image", "Generate or edit an image via Summer Studio", false, (args) =>
|
|
902
|
+
entry("summer_generate_image", "Generate or edit an image via Summer Studio", false, async (args) => {
|
|
903
|
+
const { out, ...body } = parseToolArgs(imageGenerationArgsSchema, args, "generate-image");
|
|
904
|
+
const result = await gatewayPost("/api/mcp/generate/image", body, IMAGE_GENERATION_TIMEOUT_MS);
|
|
905
|
+
if (!out)
|
|
906
|
+
return result;
|
|
907
|
+
const asset = (result.asset ?? {});
|
|
908
|
+
const url = typeof asset.fileUrl === "string" ? asset.fileUrl : typeof asset.thumbnailUrl === "string" ? asset.thumbnailUrl : null;
|
|
909
|
+
return { ...result, localPath: url ? await saveGeneratedImage(url, out) : null };
|
|
910
|
+
}),
|
|
900
911
|
entry("summer_slice_asset_sheet", "Detect and crop every asset from a generated sheet image", false, (args) => gatewayPost("/api/mcp/generate/slice-asset-sheet", { assetId: str(args, "assetId") }, 300_000)),
|
|
901
912
|
entry("summer_generate_audio", "Generate speech, sound effects, music, or dialogue", false, (args) => {
|
|
902
913
|
const body = { capability: str(args, "capability") };
|
|
@@ -10,6 +10,8 @@ export declare const FOOTER_SUFFIX = "If this entry is wrong, stale, or you devi
|
|
|
10
10
|
export declare const LINKED_FILE_EXTENSIONS: ReadonlySet<string>;
|
|
11
11
|
/** A linked file larger than this is refused rather than dumped. */
|
|
12
12
|
export declare const LINKED_FILE_MAX_BYTES: number;
|
|
13
|
+
/** How long a hosted skill read waits for the hosted Summer Engine MCP. */
|
|
14
|
+
export declare const HOSTED_READ_TIMEOUT_MS = 8000;
|
|
13
15
|
export declare const readLibraryInputShape: {
|
|
14
16
|
id: z.ZodString;
|
|
15
17
|
part: z.ZodOptional<z.ZodEnum<["skill", "resource", "all"]>>;
|
|
@@ -29,6 +31,8 @@ export interface LibraryReadDeps extends LibrarySearchDeps {
|
|
|
29
31
|
/** Root holding library/ (default: the installed package). */
|
|
30
32
|
packageRoot?: string;
|
|
31
33
|
templates?: readonly TemplateEntry[];
|
|
34
|
+
/** Reads a hosted skill's text by resource URI; throws when it cannot. */
|
|
35
|
+
readHostedResource?: (uri: string) => Promise<string>;
|
|
32
36
|
}
|
|
33
37
|
export interface LibraryReadOk {
|
|
34
38
|
ok: true;
|
|
@@ -57,6 +61,9 @@ export interface LibraryReadOk {
|
|
|
57
61
|
target: string;
|
|
58
62
|
id: string | null;
|
|
59
63
|
}>;
|
|
64
|
+
/** Hosted skills: the resource the body comes from, and why it did not load. */
|
|
65
|
+
hosted_resource?: string;
|
|
66
|
+
hosted_error?: string;
|
|
60
67
|
/** Tool records: how to reach it. */
|
|
61
68
|
mcp_tool_name?: string;
|
|
62
69
|
cli_command?: string;
|
|
@@ -31,11 +31,18 @@
|
|
|
31
31
|
* ships it; and `reference/<slug>` that is no entry falls back to the one
|
|
32
32
|
* `references/<slug>.md` an entry ships. A body render ends with its links and
|
|
33
33
|
* the id that loads each. A linked file's footer names its entry.
|
|
34
|
+
*
|
|
35
|
+
* Hosted skills: a skill whose resource.yaml names a `hosted_resource`
|
|
36
|
+
* (summer://skills/<slug>) keeps its current text on the hosted Summer Engine
|
|
37
|
+
* MCP, the text the Studio agent reads. The body is read from there with the
|
|
38
|
+
* store sign-in; without it the local SKILL.md renders with the reason and how
|
|
39
|
+
* to load the full text. The entry also resolves by that resource URI.
|
|
34
40
|
*/
|
|
35
41
|
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
36
42
|
import { join, posix, sep } from "node:path";
|
|
37
43
|
import { z } from "zod";
|
|
38
44
|
import { LIBRARY_KIND_DIRS, loadLibraryIndex, searchLibrary, } from "./library-search.js";
|
|
45
|
+
import { withHostedMcp } from "./hosted-mcp.js";
|
|
39
46
|
import { PACKAGE_ROOT } from "./package-root.js";
|
|
40
47
|
import { getTemplateRegistry } from "./templates.js";
|
|
41
48
|
export const READ_PARTS = ["skill", "resource", "all"];
|
|
@@ -60,6 +67,8 @@ export const LINKED_FILE_EXTENSIONS = new Set([
|
|
|
60
67
|
]);
|
|
61
68
|
/** A linked file larger than this is refused rather than dumped. */
|
|
62
69
|
export const LINKED_FILE_MAX_BYTES = 256 * 1024;
|
|
70
|
+
/** How long a hosted skill read waits for the hosted Summer Engine MCP. */
|
|
71
|
+
export const HOSTED_READ_TIMEOUT_MS = 8_000;
|
|
63
72
|
export const readLibraryInputShape = {
|
|
64
73
|
id: z
|
|
65
74
|
.string()
|
|
@@ -81,13 +90,13 @@ export function entryIdWithHash(id, contentHash) {
|
|
|
81
90
|
return hash ? `${id}@${hash}` : id;
|
|
82
91
|
}
|
|
83
92
|
// ── Resolution ─────────────────────────────────────────────────────────────
|
|
84
|
-
/** Exact id, an id with an @hash suffix (as the footer prints it),
|
|
85
|
-
* slug that names exactly one entry. */
|
|
93
|
+
/** Exact id, an id with an @hash suffix (as the footer prints it), a hosted
|
|
94
|
+
* skill's resource URI, or a bare slug that names exactly one entry. */
|
|
86
95
|
function resolveEntry(requested, entries) {
|
|
87
96
|
const bare = requested.trim().replace(/@[a-f0-9]+$/i, "");
|
|
88
97
|
if (!bare)
|
|
89
98
|
return null;
|
|
90
|
-
const exact = entries.find((entry) => entry.id === bare);
|
|
99
|
+
const exact = entries.find((entry) => entry.id === bare || entry.hosted_resource === bare);
|
|
91
100
|
if (exact)
|
|
92
101
|
return exact;
|
|
93
102
|
if (!bare.includes("/")) {
|
|
@@ -324,7 +333,7 @@ function relatedMap(entry) {
|
|
|
324
333
|
}
|
|
325
334
|
return out;
|
|
326
335
|
}
|
|
327
|
-
function header(entry, slug, related) {
|
|
336
|
+
function header(entry, slug, related, hostedLoaded) {
|
|
328
337
|
const lines = [];
|
|
329
338
|
lines.push(`${entry.id} — ${entry.kind} v${entry.version ?? "?"} (${entry.status ?? "stable"})`);
|
|
330
339
|
if (entry.summary)
|
|
@@ -338,7 +347,10 @@ function header(entry, slug, related) {
|
|
|
338
347
|
const relatedIds = Object.values(related).flat();
|
|
339
348
|
if (relatedIds.length > 0)
|
|
340
349
|
lines.push(`related: ${relatedIds.join(", ")}`);
|
|
341
|
-
if (
|
|
350
|
+
if (hostedLoaded) {
|
|
351
|
+
lines.push(`The body is the current text from the hosted Summer Engine MCP (also the \`${slug}\` MCP prompt and the resource ${entry.hosted_resource}). Follow it, do not paraphrase it.`);
|
|
352
|
+
}
|
|
353
|
+
else if (entry.kind === "skill") {
|
|
342
354
|
lines.push(`Invoke: the \`${slug}\` skill in your host (Claude Code: /${slug}); installed under its bare slug by \`summer setup\`. The body follows — follow it, do not paraphrase it.`);
|
|
343
355
|
}
|
|
344
356
|
return lines.join("\n");
|
|
@@ -451,7 +463,21 @@ export async function readLibraryEntry(requestedId, part = "all", deps = {}) {
|
|
|
451
463
|
let bodyTitle;
|
|
452
464
|
let body;
|
|
453
465
|
let links = [];
|
|
454
|
-
|
|
466
|
+
let hostedText = null;
|
|
467
|
+
let hostedError;
|
|
468
|
+
if (kind === "skill" && entry.hosted_resource && part !== "resource") {
|
|
469
|
+
try {
|
|
470
|
+
hostedText = (await (deps.readHostedResource ?? readHostedSkill)(entry.hosted_resource)).replace(/\s+$/, "");
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
hostedError = error instanceof Error ? error.message : String(error);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (hostedText !== null) {
|
|
477
|
+
bodyTitle = `${entry.hosted_resource} (hosted Summer Engine MCP)`;
|
|
478
|
+
body = hostedText;
|
|
479
|
+
}
|
|
480
|
+
else if (kind === "tool") {
|
|
455
481
|
bodyTitle = "how to call";
|
|
456
482
|
body = toolBody(entry, slug);
|
|
457
483
|
}
|
|
@@ -474,11 +500,14 @@ export async function readLibraryEntry(requestedId, part = "all", deps = {}) {
|
|
|
474
500
|
body = `(no body file shipped for this ${kind}; the descriptor below is all there is)`;
|
|
475
501
|
}
|
|
476
502
|
}
|
|
477
|
-
const sections = [header(entry, slug, related)];
|
|
503
|
+
const sections = [header(entry, slug, related, hostedText !== null)];
|
|
478
504
|
if (part === "skill" || part === "all") {
|
|
479
505
|
sections.push(`--- ${bodyTitle} ---\n${body}`);
|
|
480
506
|
if (links.length > 0)
|
|
481
507
|
sections.push(linksSection(links));
|
|
508
|
+
if (hostedError !== undefined) {
|
|
509
|
+
sections.push(`--- hosted text not loaded ---\nThe full current text of this skill is the resource ${entry.hosted_resource} on the hosted Summer Engine MCP, and it could not be read: ${hostedError}\nThe body above is only the local summary: load the full text before you act (here after the fix, or as the \`${slug}\` MCP prompt or resource in a host connected to the hosted Summer Engine MCP).`);
|
|
510
|
+
}
|
|
482
511
|
}
|
|
483
512
|
if (part === "resource" || part === "all")
|
|
484
513
|
sections.push(`--- ${relPath}/resource.yaml ---\n${resourceYaml}`);
|
|
@@ -486,6 +515,10 @@ export async function readLibraryEntry(requestedId, part = "all", deps = {}) {
|
|
|
486
515
|
const result = { ...base, text: sections.join("\n\n") };
|
|
487
516
|
if (bodyFile)
|
|
488
517
|
result.body_file = bodyFile;
|
|
518
|
+
if (entry.hosted_resource)
|
|
519
|
+
result.hosted_resource = entry.hosted_resource;
|
|
520
|
+
if (hostedError !== undefined)
|
|
521
|
+
result.hosted_error = hostedError;
|
|
489
522
|
if (links.length > 0 && part !== "resource")
|
|
490
523
|
result.links = links;
|
|
491
524
|
if (kind === "tool") {
|
|
@@ -499,6 +532,25 @@ export async function readLibraryEntry(requestedId, part = "all", deps = {}) {
|
|
|
499
532
|
}
|
|
500
533
|
return result;
|
|
501
534
|
}
|
|
535
|
+
/** A hosted skill's text, read from the hosted Summer Engine MCP with the store sign-in. */
|
|
536
|
+
async function readHostedSkill(uri) {
|
|
537
|
+
let timer;
|
|
538
|
+
const timeout = new Promise((_, reject) => {
|
|
539
|
+
timer = setTimeout(() => reject(new Error(`the hosted Summer Engine MCP did not answer within ${HOSTED_READ_TIMEOUT_MS / 1000} s.`)), HOSTED_READ_TIMEOUT_MS);
|
|
540
|
+
timer.unref?.();
|
|
541
|
+
});
|
|
542
|
+
try {
|
|
543
|
+
const result = await Promise.race([withHostedMcp((client) => client.readResource({ uri })), timeout]);
|
|
544
|
+
const contents = Array.isArray(result.contents) ? result.contents : [];
|
|
545
|
+
const text = contents.map((content) => (typeof content.text === "string" ? content.text : "")).join("\n").trim();
|
|
546
|
+
if (!text)
|
|
547
|
+
throw new Error("the hosted Summer Engine MCP returned no text for it.");
|
|
548
|
+
return text;
|
|
549
|
+
}
|
|
550
|
+
finally {
|
|
551
|
+
clearTimeout(timer);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
502
554
|
function safeTemplates() {
|
|
503
555
|
try {
|
|
504
556
|
return getTemplateRegistry();
|
|
@@ -49,6 +49,8 @@ export interface LibraryIndexEntry {
|
|
|
49
49
|
cli_command?: string;
|
|
50
50
|
authority?: Record<string, boolean>;
|
|
51
51
|
recommended?: boolean;
|
|
52
|
+
/** The hosted Summer Engine MCP resource holding the skill's current text. */
|
|
53
|
+
hosted_resource?: string;
|
|
52
54
|
}
|
|
53
55
|
export type MatchedBy = "lexical" | "semantic";
|
|
54
56
|
export interface LibrarySearchHit {
|
|
@@ -45,6 +45,31 @@ export declare function resolveAgentSelection(agentArg: string | undefined, agen
|
|
|
45
45
|
/** `--scope` with a user-facing error; absent means "user". */
|
|
46
46
|
export declare function resolveConfigScope(scopeOpt: string | undefined): ConfigScope;
|
|
47
47
|
export declare function configureAgentMcp(options: AgentConfigOptions): Promise<AgentConfigResult>;
|
|
48
|
+
/** The Summer Games player MCP: friends, parties, messages and the player's
|
|
49
|
+
* inbox, as the signed-in player. A remote server; the agent signs in to it
|
|
50
|
+
* with OAuth in the browser, so setup writes no credential. */
|
|
51
|
+
export declare const SUMMER_GAMES_MCP_SERVER_NAME = "summer-games";
|
|
52
|
+
export declare const SUMMER_GAMES_MCP_URL = "https://mcp.summer.games/mcp";
|
|
53
|
+
export interface RemoteMcpResult {
|
|
54
|
+
/** The entry's name: summer-games, or the name the person already gave it. */
|
|
55
|
+
serverName: string;
|
|
56
|
+
url: string;
|
|
57
|
+
path: string;
|
|
58
|
+
changed: boolean;
|
|
59
|
+
wrote: boolean;
|
|
60
|
+
/** Set when the server was already listed under another name (e.g. a
|
|
61
|
+
* hand-added "summer"); setup then leaves the file alone. */
|
|
62
|
+
existingName?: string;
|
|
63
|
+
/** What the person does once to sign in (the agent opens summer.games). */
|
|
64
|
+
signIn: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Adds the Summer Games MCP next to summer-engine in the same config file, in
|
|
68
|
+
* the agent's own entry shape (remoteMcp in agent-table.ts). An entry that
|
|
69
|
+
* already points at the same URL, under any name, counts as added and is left
|
|
70
|
+
* as it is. Returns null for agents without remoteMcp.
|
|
71
|
+
*/
|
|
72
|
+
export declare function configureSummerGamesMcp(options: Pick<AgentConfigOptions, "agent" | "scope" | "dryRun" | "cwd" | "env">): Promise<RemoteMcpResult | null>;
|
|
48
73
|
export declare const DEFAULT_CHANNEL = "latest";
|
|
49
74
|
/** npm dist-tags are lowercase words like `latest`, `next`, `beta`, `v3-preview`;
|
|
50
75
|
* anything that could be read as a version or a path is refused (a bare
|
|
@@ -60,4 +85,4 @@ export declare function mcpEntry(format: McpFormat, server: StdioMcpServerConfig
|
|
|
60
85
|
export declare function resolvePackageRoot(): string;
|
|
61
86
|
/** Directory name Gemini expects the extension under; the manifest `name` must match it. */
|
|
62
87
|
export declare const GEMINI_EXTENSION_DIR_NAME = "summer-engine";
|
|
63
|
-
export declare function upsertTomlArrayBlock(content: string, block: string): string;
|
|
88
|
+
export declare function upsertTomlArrayBlock(content: string, block: string, name?: string): string;
|