summer-engine 3.6.0 → 3.7.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +2 -2
- package/.factory-plugin/plugin.json +2 -2
- package/CHANGELOG.md +8 -0
- package/README.md +0 -4
- package/dist/cli/commands/login.d.ts +0 -10
- package/dist/cli/commands/login.js +2 -82
- package/dist/cli/index.js +0 -4
- package/dist/core/auth.d.ts +0 -8
- package/dist/core/auth.js +5 -35
- package/dist/core/capabilities/seeing/args.d.ts +2 -2
- package/dist/core/capabilities/seeing/seeing.d.ts +1 -1
- package/dist/core/capabilities/sfx-choices.d.ts +2 -15
- package/dist/core/capabilities/sfx-choices.js +26 -64
- package/dist/core/capabilities/tool-dispatch.js +2 -54
- package/dist/core/config.d.ts +1 -7
- package/dist/core/config.js +5 -75
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.js +6 -5
- package/dist/mcp/tools/creator-tools.js +0 -71
- package/dist/mcp/tools/generate-tools.js +3 -4
- package/gemini-extension.json +1 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +2 -5
- package/library/skills/publish-your-game/SKILL.md +2 -0
- package/library/tools/generate-sfx-choices/resource.yaml +2 -2
- package/package.json +1 -1
- package/registry/generated/aliases.json +0 -3
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +1 -1
- package/registry/generated/index.json +4 -107
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +2 -2
- package/registry/generated/plugin.codex.json +3 -3
- package/registry/generated/plugin.cursor.json +2 -2
- package/registry/generated/plugin.factory.json +2 -2
- package/dist/cli/commands/publish.d.ts +0 -2
- package/dist/cli/commands/publish.js +0 -21
- package/dist/cli/commands/releases.d.ts +0 -2
- package/dist/cli/commands/releases.js +0 -20
- package/dist/core/capabilities/creator.d.ts +0 -65
- package/dist/core/capabilities/creator.js +0 -374
- package/library/tools/creator-config/resource.yaml +0 -63
- package/library/tools/creator-publish/resource.yaml +0 -74
- package/library/tools/creator-releases/resource.yaml +0 -53
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"_generated": "GENERATED from integrations/claude — do not edit; npm run generate:registry",
|
|
4
4
|
"name": "summer",
|
|
5
|
-
"version": "3.
|
|
6
|
-
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a
|
|
5
|
+
"version": "3.7.0",
|
|
6
|
+
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 104-tool MCP bridge to the local desktop app.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Summer Engine",
|
|
9
9
|
"email": "founders@summerengine.com",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/codex — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"version": "3.
|
|
5
|
-
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a
|
|
4
|
+
"version": "3.7.0",
|
|
5
|
+
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 104-tool MCP bridge. Build games by talking.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
8
8
|
"email": "founders@summerengine.com",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"interface": {
|
|
152
152
|
"displayName": "Summer",
|
|
153
153
|
"shortDescription": "Superpowers for AI game dev. Build games by talking.",
|
|
154
|
-
"longDescription": "Summer turns Codex into a game-dev studio for Summer Engine. Users make Summer games with the Summer SDK and GDScript. The plugin adds game-dev skills, lifecycle hooks, and a
|
|
154
|
+
"longDescription": "Summer turns Codex into a game-dev studio for Summer Engine. Users make Summer games with the Summer SDK and GDScript. The plugin adds game-dev skills, lifecycle hooks, and a 104-tool MCP bridge for guarded project files, scene editing, asset import, play mode, diagnostics, generation, and creator workflows. Git, shell, and grep stay with the host agent. Summer follows its upstream technical base continuously; version-sensitive work uses the bundled compatibility reference instead of a fixed onboarding version. Session-start orients the session. Pre-commit doctor (opt-in) blocks bad commits.",
|
|
155
155
|
"developerName": "Summer Engine",
|
|
156
156
|
"category": "Coding",
|
|
157
157
|
"capabilities": [
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_generated": "GENERATED from integrations/cursor — do not edit; npm run generate:registry",
|
|
4
4
|
"name": "summer",
|
|
5
5
|
"displayName": "Summer",
|
|
6
|
-
"version": "3.
|
|
7
|
-
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a
|
|
6
|
+
"version": "3.7.0",
|
|
7
|
+
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 104-tool MCP bridge to the local desktop app.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Summer Engine",
|
|
10
10
|
"email": "founders@summerengine.com"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/factory — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a
|
|
5
|
-
"version": "3.
|
|
4
|
+
"description": "Agent tooling for Summer Engine: game-dev skills, lifecycle hooks, and a 104-tool MCP bridge to the local desktop app.",
|
|
5
|
+
"version": "3.7.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
8
8
|
"email": "founders@summerengine.com",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { publishCreator } from "../../core/capabilities/creator.js";
|
|
3
|
-
export const publishCommand = new Command("publish")
|
|
4
|
-
.description("Deprecated: publish a .pck to the legacy creator API (use `summer tool export-game`, then `summer tool publish-build`)")
|
|
5
|
-
.argument("[project]", "Project root. Defaults to the current directory.")
|
|
6
|
-
.requiredOption("--artifact <path>", "Exact exported Summer .pck artifact")
|
|
7
|
-
.requiredOption("--version <value>", "Immutable release version")
|
|
8
|
-
.option("--manifest <path>", "Optional JSON release manifest")
|
|
9
|
-
.option("--project-id <id>", "Creator project ID")
|
|
10
|
-
.option("--channel <name>", "Release channel")
|
|
11
|
-
.option("--notes <text>", "Release notes")
|
|
12
|
-
.option("--confirm", "Confirm the exact project and channel after reviewing them")
|
|
13
|
-
.action(async (project, opts) => {
|
|
14
|
-
console.error("summer publish is deprecated: run `summer tool export-game`, then `summer tool publish-build`, to upload the summer.games .zip to your game.");
|
|
15
|
-
const result = await publishCreator({
|
|
16
|
-
project,
|
|
17
|
-
...opts,
|
|
18
|
-
face: "cli",
|
|
19
|
-
});
|
|
20
|
-
console.log(JSON.stringify(result, null, 2));
|
|
21
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { listCreatorReleases } from "../../core/capabilities/creator.js";
|
|
3
|
-
export const releasesCommand = new Command("releases")
|
|
4
|
-
.description("List creator releases for a project")
|
|
5
|
-
.option("--project-id <id>", "Creator project ID")
|
|
6
|
-
.option("--limit <count>", "Maximum releases to return", "20")
|
|
7
|
-
.option("--cursor <value>", "Opaque cursor returned by the previous page")
|
|
8
|
-
.action(async (opts) => {
|
|
9
|
-
const limit = Number.parseInt(opts.limit, 10);
|
|
10
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
11
|
-
throw new Error("--limit must be between 1 and 100. Recovery: pass a whole number in that range.");
|
|
12
|
-
}
|
|
13
|
-
const result = await listCreatorReleases({
|
|
14
|
-
projectId: opts.projectId,
|
|
15
|
-
limit,
|
|
16
|
-
cursor: opts.cursor,
|
|
17
|
-
face: "cli",
|
|
18
|
-
});
|
|
19
|
-
console.log(JSON.stringify(result, null, 2));
|
|
20
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export type CreatorOperation = "publish" | "releases";
|
|
2
|
-
export type CreatorFace = "cli" | "mcp";
|
|
3
|
-
export declare const CREATOR_API_CONTRACT: "summer.creator.v1";
|
|
4
|
-
export declare class CreatorOperationError extends Error {
|
|
5
|
-
readonly code: string;
|
|
6
|
-
readonly operation: CreatorOperation;
|
|
7
|
-
readonly recovery: string;
|
|
8
|
-
readonly status?: number | undefined;
|
|
9
|
-
constructor(code: string, operation: CreatorOperation, message: string, recovery: string, status?: number | undefined);
|
|
10
|
-
}
|
|
11
|
-
export interface CreatorClientDependencies {
|
|
12
|
-
fetch: typeof fetch;
|
|
13
|
-
now: () => Date;
|
|
14
|
-
}
|
|
15
|
-
export interface PublishCreatorInput {
|
|
16
|
-
project?: string;
|
|
17
|
-
artifact?: string;
|
|
18
|
-
manifest?: string;
|
|
19
|
-
version?: string;
|
|
20
|
-
projectId?: string;
|
|
21
|
-
channel?: string;
|
|
22
|
-
notes?: string;
|
|
23
|
-
confirm?: boolean;
|
|
24
|
-
face: CreatorFace;
|
|
25
|
-
}
|
|
26
|
-
export interface CreatorPublishResult {
|
|
27
|
-
ok: true;
|
|
28
|
-
contract: typeof CREATOR_API_CONTRACT;
|
|
29
|
-
operation: "publish";
|
|
30
|
-
projectId: string;
|
|
31
|
-
releaseId: string;
|
|
32
|
-
version: string;
|
|
33
|
-
sha256: string;
|
|
34
|
-
sizeBytes: number;
|
|
35
|
-
status: string;
|
|
36
|
-
detail: string | null;
|
|
37
|
-
}
|
|
38
|
-
export declare function publishCreator(input: PublishCreatorInput, overrides?: Partial<CreatorClientDependencies>): Promise<CreatorPublishResult>;
|
|
39
|
-
export interface ListCreatorReleasesInput {
|
|
40
|
-
projectId?: string;
|
|
41
|
-
limit?: number;
|
|
42
|
-
cursor?: string;
|
|
43
|
-
face: CreatorFace;
|
|
44
|
-
}
|
|
45
|
-
export interface CreatorRelease {
|
|
46
|
-
releaseId: string;
|
|
47
|
-
gameId: string;
|
|
48
|
-
version: string;
|
|
49
|
-
state: "published" | "pending_review" | "retired";
|
|
50
|
-
gameStatus: string;
|
|
51
|
-
sha256: string | null;
|
|
52
|
-
sizeBytes: number | null;
|
|
53
|
-
changelog: string | null;
|
|
54
|
-
submittedAt: string;
|
|
55
|
-
artifactPlane: "r2" | "legacy";
|
|
56
|
-
}
|
|
57
|
-
export interface CreatorReleasesResult {
|
|
58
|
-
ok: true;
|
|
59
|
-
contract: typeof CREATOR_API_CONTRACT;
|
|
60
|
-
operation: "releases";
|
|
61
|
-
projectId: string;
|
|
62
|
-
releases: CreatorRelease[];
|
|
63
|
-
nextCursor: string | null;
|
|
64
|
-
}
|
|
65
|
-
export declare function listCreatorReleases(input: ListCreatorReleasesInput, overrides?: Partial<CreatorClientDependencies>): Promise<CreatorReleasesResult>;
|
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { createReadStream } from "node:fs";
|
|
3
|
-
import { lstat, readFile } from "node:fs/promises";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import { getCredentialMetadata, getCreatorToken, } from "../auth.js";
|
|
6
|
-
import { getCreatorApiUrl, readSummerConfig, } from "../config.js";
|
|
7
|
-
import { appendStoreJsonLine } from "../store.js";
|
|
8
|
-
import { readJsonResponse } from "../util/http.js";
|
|
9
|
-
export const CREATOR_API_CONTRACT = "summer.creator.v1";
|
|
10
|
-
const CREATOR_PUBLISH_PATH = "/api/creator/v1/publish";
|
|
11
|
-
const CREATOR_RELEASES_PATH = "/api/creator/v1/releases";
|
|
12
|
-
const REQUIRED_SCOPE = "publish";
|
|
13
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14
|
-
const VERSION = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/;
|
|
15
|
-
export class CreatorOperationError extends Error {
|
|
16
|
-
code;
|
|
17
|
-
operation;
|
|
18
|
-
recovery;
|
|
19
|
-
status;
|
|
20
|
-
constructor(code, operation, message, recovery, status) {
|
|
21
|
-
super(`${message} ${recovery}`);
|
|
22
|
-
this.code = code;
|
|
23
|
-
this.operation = operation;
|
|
24
|
-
this.recovery = recovery;
|
|
25
|
-
this.status = status;
|
|
26
|
-
this.name = "CreatorOperationError";
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const defaultDependencies = {
|
|
30
|
-
fetch: (input, init) => globalThis.fetch(input, init),
|
|
31
|
-
now: () => new Date(),
|
|
32
|
-
};
|
|
33
|
-
async function resolveProjectId(explicit) {
|
|
34
|
-
const projectId = explicit?.trim() || (await readSummerConfig()).creator?.projectId;
|
|
35
|
-
if (!projectId) {
|
|
36
|
-
throw new Error('No creator project is selected. Recovery: pass --project-id <uuid>, or run "summer config set creator.projectId <uuid>".');
|
|
37
|
-
}
|
|
38
|
-
if (!UUID.test(projectId)) {
|
|
39
|
-
throw new Error(`Creator project ID "${projectId}" is not a UUID. Recovery: copy the Summer game ID from the creator dashboard and configure it again.`);
|
|
40
|
-
}
|
|
41
|
-
return projectId;
|
|
42
|
-
}
|
|
43
|
-
async function requireCreatorCredential(operation) {
|
|
44
|
-
const token = await getCreatorToken();
|
|
45
|
-
if (!token) {
|
|
46
|
-
throw new CreatorOperationError("creator_token_required", operation, "Creator publishing needs a separate Summer Platform creator API token.", 'Recovery: run "summer login --creator", mint a token with the exact "publish" scope in the opened browser page, and paste its one-time sc_ value. Your core Summer login will not be changed.');
|
|
47
|
-
}
|
|
48
|
-
const metadata = await getCredentialMetadata();
|
|
49
|
-
if (!metadata.creator?.scopes.includes(REQUIRED_SCOPE)) {
|
|
50
|
-
throw new CreatorOperationError("creator_scope_missing", operation, `The locally recorded creator credential does not declare the exact "${REQUIRED_SCOPE}" scope.`, 'Recovery: run "summer login --creator --force", mint a new publish-scoped token, and retry. The server will still independently verify the scope.');
|
|
51
|
-
}
|
|
52
|
-
return token;
|
|
53
|
-
}
|
|
54
|
-
function recoveryForStatus(operation, status) {
|
|
55
|
-
if (status === 401 || status === 403) {
|
|
56
|
-
return 'Recovery: run "summer login --creator --force", mint a non-revoked token with the exact "publish" scope, and retry.';
|
|
57
|
-
}
|
|
58
|
-
if (status === 404) {
|
|
59
|
-
return 'Recovery: check creator.projectId against the Summer creator dashboard and make sure that game belongs to the token owner.';
|
|
60
|
-
}
|
|
61
|
-
if (status === 409 && operation === "publish") {
|
|
62
|
-
return "Recovery: read the server detail, bump an immutable version when required, then repeat the unconfirmed command to review the new exact target.";
|
|
63
|
-
}
|
|
64
|
-
return `Recovery: retry once; if it repeats, check creator.apiUrl and the Summer Platform creator API health.`;
|
|
65
|
-
}
|
|
66
|
-
async function readCreatorResponse(response, operation) {
|
|
67
|
-
const { json, parsed } = await readJsonResponse(response);
|
|
68
|
-
const payload = json;
|
|
69
|
-
if (!parsed) {
|
|
70
|
-
throw new CreatorOperationError("creator_invalid_response", operation, `The creator API returned a non-JSON response (${response.status}).`, recoveryForStatus(operation, response.status), response.status);
|
|
71
|
-
}
|
|
72
|
-
if (!response.ok) {
|
|
73
|
-
const code = typeof payload.error === "string" ? payload.error : "creator_request_failed";
|
|
74
|
-
const detail = typeof payload.detail === "string"
|
|
75
|
-
? payload.detail
|
|
76
|
-
: `The creator API refused the request (${response.status}).`;
|
|
77
|
-
const recovery = typeof payload.recovery === "string"
|
|
78
|
-
? payload.recovery
|
|
79
|
-
: recoveryForStatus(operation, response.status);
|
|
80
|
-
throw new CreatorOperationError(code, operation, detail, recovery, response.status);
|
|
81
|
-
}
|
|
82
|
-
if (payload.contract !== CREATOR_API_CONTRACT) {
|
|
83
|
-
throw new CreatorOperationError("creator_contract_mismatch", operation, `The creator API did not return contract ${CREATOR_API_CONTRACT}.`, "Recovery: upgrade the Summer CLI and retry; if it repeats, the configured creator API is incompatible.", response.status);
|
|
84
|
-
}
|
|
85
|
-
return payload;
|
|
86
|
-
}
|
|
87
|
-
async function creatorRequest(operation, path, token, deps, init = {}) {
|
|
88
|
-
let response;
|
|
89
|
-
try {
|
|
90
|
-
response = await deps.fetch(`${await getCreatorApiUrl()}${path}`, {
|
|
91
|
-
...init,
|
|
92
|
-
headers: {
|
|
93
|
-
accept: "application/json",
|
|
94
|
-
authorization: `Bearer ${token}`,
|
|
95
|
-
...(init.body ? { "content-type": "application/json" } : {}),
|
|
96
|
-
...init.headers,
|
|
97
|
-
},
|
|
98
|
-
signal: AbortSignal.timeout(operation === "publish" ? 300_000 : 30_000),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
throw new CreatorOperationError("creator_network_failed", operation, `The creator API request failed: ${error instanceof Error ? error.message : String(error)}.`, "Recovery: check your network and creator.apiUrl, then retry. No successful release is being claimed.");
|
|
103
|
-
}
|
|
104
|
-
return readCreatorResponse(response, operation);
|
|
105
|
-
}
|
|
106
|
-
async function inspectArtifact(path) {
|
|
107
|
-
const artifactPath = resolve(path);
|
|
108
|
-
const info = await lstat(artifactPath).catch(() => null);
|
|
109
|
-
if (!info || !info.isFile() || info.isSymbolicLink()) {
|
|
110
|
-
throw new Error(`Release artifact ${artifactPath} must be a real regular file, not a missing path, directory, or symlink.`);
|
|
111
|
-
}
|
|
112
|
-
if (!artifactPath.toLowerCase().endsWith(".pck")) {
|
|
113
|
-
throw new Error(`Release artifact ${artifactPath} is not a .pck. Recovery: export a Summer PCK and pass its exact path with --artifact.`);
|
|
114
|
-
}
|
|
115
|
-
if (info.size <= 0) {
|
|
116
|
-
throw new Error(`Release artifact ${artifactPath} is empty. Recovery: export the Summer PCK again and verify it locally before publishing.`);
|
|
117
|
-
}
|
|
118
|
-
const hash = createHash("sha256");
|
|
119
|
-
for await (const chunk of createReadStream(artifactPath))
|
|
120
|
-
hash.update(chunk);
|
|
121
|
-
return {
|
|
122
|
-
path: artifactPath,
|
|
123
|
-
sizeBytes: info.size,
|
|
124
|
-
sha256: hash.digest("hex"),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
async function readManifest(path) {
|
|
128
|
-
if (!path)
|
|
129
|
-
return undefined;
|
|
130
|
-
const manifestPath = resolve(path);
|
|
131
|
-
try {
|
|
132
|
-
return JSON.parse(await readFile(manifestPath, "utf8"));
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
throw new Error(`Cannot read release manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}. Recovery: pass a valid JSON manifest or omit --manifest.`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function assertUploadUrl(value) {
|
|
139
|
-
if (typeof value !== "string") {
|
|
140
|
-
throw new CreatorOperationError("creator_invalid_response", "publish", "The prepare response did not contain an uploadUrl.", "Recovery: retry once; if it repeats, report the Summer Platform creator API as unhealthy.");
|
|
141
|
-
}
|
|
142
|
-
let url;
|
|
143
|
-
try {
|
|
144
|
-
url = new URL(value);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
throw new CreatorOperationError("creator_unsafe_upload_url", "publish", "The creator API returned a malformed upload URL.", "Recovery: do not upload the artifact; report the creator API response as a security issue.");
|
|
148
|
-
}
|
|
149
|
-
const local = url.hostname === "localhost" ||
|
|
150
|
-
url.hostname === "127.0.0.1" ||
|
|
151
|
-
url.hostname === "[::1]";
|
|
152
|
-
if (url.username ||
|
|
153
|
-
url.password ||
|
|
154
|
-
(url.protocol !== "https:" && !(local && url.protocol === "http:"))) {
|
|
155
|
-
throw new CreatorOperationError("creator_unsafe_upload_url", "publish", "The creator API returned an unsafe upload URL.", "Recovery: do not upload the artifact; report the creator API response as a security issue.");
|
|
156
|
-
}
|
|
157
|
-
return url.toString();
|
|
158
|
-
}
|
|
159
|
-
function assertUploadHeaders(value) {
|
|
160
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
161
|
-
throw new CreatorOperationError("creator_invalid_response", "publish", "The prepare response did not contain signed upload headers.", "Recovery: retry once; if it repeats, report the Summer Platform creator API as unhealthy.");
|
|
162
|
-
}
|
|
163
|
-
const headers = Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
164
|
-
if (headers["content-type"] !== "application/octet-stream" ||
|
|
165
|
-
headers["if-none-match"] !== "*") {
|
|
166
|
-
throw new CreatorOperationError("creator_unsafe_upload_headers", "publish", "The prepare response did not bind the expected content type and write-once header.", "Recovery: do not upload the artifact; report the creator API response as a security issue.");
|
|
167
|
-
}
|
|
168
|
-
return headers;
|
|
169
|
-
}
|
|
170
|
-
async function uploadArtifact(uploadUrl, headers, artifactPath, deps) {
|
|
171
|
-
let response;
|
|
172
|
-
try {
|
|
173
|
-
response = await deps.fetch(uploadUrl, {
|
|
174
|
-
method: "PUT",
|
|
175
|
-
headers,
|
|
176
|
-
body: createReadStream(artifactPath),
|
|
177
|
-
// Node fetch requires this for a streaming request body. It is a Node
|
|
178
|
-
// extension and therefore absent from the DOM RequestInit declaration.
|
|
179
|
-
duplex: "half",
|
|
180
|
-
signal: AbortSignal.timeout(600_000),
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
throw new CreatorOperationError("creator_upload_failed", "publish", `The artifact upload failed: ${error instanceof Error ? error.message : String(error)}.`, "Recovery: rerun the unconfirmed publish command to recompute and review the exact artifact, then retry.");
|
|
185
|
-
}
|
|
186
|
-
if (!response.ok) {
|
|
187
|
-
throw new CreatorOperationError("creator_upload_refused", "publish", `The immutable artifact upload was refused (${response.status}).`, response.status === 412
|
|
188
|
-
? "Recovery: the content-addressed key is already occupied. Do not overwrite it; verify the project/version and retry with a new immutable version."
|
|
189
|
-
: "Recovery: repeat prepare and reproduce every returned signed header exactly, then retry.", response.status);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
export async function publishCreator(input, overrides = {}) {
|
|
193
|
-
const deps = { ...defaultDependencies, ...overrides };
|
|
194
|
-
const project = resolve(input.project ?? process.cwd());
|
|
195
|
-
const projectId = await resolveProjectId(input.projectId);
|
|
196
|
-
const channel = input.channel?.trim() ||
|
|
197
|
-
(await readSummerConfig()).creator?.channel ||
|
|
198
|
-
"production";
|
|
199
|
-
if (channel !== "production") {
|
|
200
|
-
throw new CreatorOperationError("creator_channel_unsupported", "publish", `The Summer Platform creator API has no "${channel}" release channel.`, 'Recovery: use --channel production or run "summer config set creator.channel production". No release was created.');
|
|
201
|
-
}
|
|
202
|
-
const version = input.version?.trim();
|
|
203
|
-
if (!version || !VERSION.test(version)) {
|
|
204
|
-
throw new Error("A release version is required and must be 1–32 characters of letters, numbers, dots, underscores, or hyphens. Recovery: pass --version <value>.");
|
|
205
|
-
}
|
|
206
|
-
if (!input.artifact) {
|
|
207
|
-
throw new Error("A release artifact is required. Recovery: export a Summer .pck and pass --artifact <path>; the CLI never guesses an export layout.");
|
|
208
|
-
}
|
|
209
|
-
const artifact = await inspectArtifact(input.artifact);
|
|
210
|
-
const manifest = await readManifest(input.manifest);
|
|
211
|
-
const auditBase = {
|
|
212
|
-
at: deps.now().toISOString(),
|
|
213
|
-
operation: "publish",
|
|
214
|
-
face: input.face,
|
|
215
|
-
project,
|
|
216
|
-
projectId,
|
|
217
|
-
channel,
|
|
218
|
-
artifact: artifact.path,
|
|
219
|
-
version,
|
|
220
|
-
sha256: artifact.sha256,
|
|
221
|
-
sizeBytes: artifact.sizeBytes,
|
|
222
|
-
notes: input.notes?.trim() || null,
|
|
223
|
-
};
|
|
224
|
-
if (!input.confirm) {
|
|
225
|
-
await appendStoreJsonLine("creator-audit.jsonl", {
|
|
226
|
-
...auditBase,
|
|
227
|
-
outcome: "confirmation_required",
|
|
228
|
-
});
|
|
229
|
-
throw new CreatorOperationError("publish_confirmation_required", "publish", `Publishing changes project ${projectId}: version ${version}, sha256 ${artifact.sha256}, ${artifact.sizeBytes} bytes from ${artifact.path}.`, "Recovery: show this exact project, version, digest, size, and artifact path to the user. Only after approval, rerun with --confirm (or confirm=true). No release was created.");
|
|
230
|
-
}
|
|
231
|
-
let token;
|
|
232
|
-
try {
|
|
233
|
-
token = await requireCreatorCredential("publish");
|
|
234
|
-
await appendStoreJsonLine("creator-audit.jsonl", {
|
|
235
|
-
...auditBase,
|
|
236
|
-
outcome: "started",
|
|
237
|
-
tokenAudience: "summercraft-creator",
|
|
238
|
-
advisoryTokenScopes: [REQUIRED_SCOPE],
|
|
239
|
-
});
|
|
240
|
-
const confirmation = {
|
|
241
|
-
gameId: projectId,
|
|
242
|
-
version,
|
|
243
|
-
sha256: artifact.sha256,
|
|
244
|
-
};
|
|
245
|
-
const prepareBody = {
|
|
246
|
-
contract: CREATOR_API_CONTRACT,
|
|
247
|
-
operation: "prepare",
|
|
248
|
-
...confirmation,
|
|
249
|
-
sizeBytes: artifact.sizeBytes,
|
|
250
|
-
contentType: "application/octet-stream",
|
|
251
|
-
...(input.notes?.trim() ? { changelog: input.notes.trim() } : {}),
|
|
252
|
-
...(manifest !== undefined ? { manifest } : {}),
|
|
253
|
-
confirmation,
|
|
254
|
-
};
|
|
255
|
-
const prepared = await creatorRequest("publish", CREATOR_PUBLISH_PATH, token, deps, {
|
|
256
|
-
method: "POST",
|
|
257
|
-
body: JSON.stringify(prepareBody),
|
|
258
|
-
});
|
|
259
|
-
if (prepared.operation !== "prepare") {
|
|
260
|
-
throw new CreatorOperationError("creator_invalid_response", "publish", "The prepare response named the wrong operation.", "Recovery: do not upload the artifact; upgrade the Summer CLI or report the creator API as incompatible.");
|
|
261
|
-
}
|
|
262
|
-
if (prepared.method !== "PUT" ||
|
|
263
|
-
prepared.finalizeUrl !== CREATOR_PUBLISH_PATH) {
|
|
264
|
-
throw new CreatorOperationError("creator_invalid_response", "publish", "The prepare response did not bind the expected PUT and versioned finalize route.", "Recovery: do not upload the artifact; upgrade the Summer CLI or report the creator API as incompatible.");
|
|
265
|
-
}
|
|
266
|
-
await uploadArtifact(assertUploadUrl(prepared.uploadUrl), assertUploadHeaders(prepared.headers), artifact.path, deps);
|
|
267
|
-
const finalized = await creatorRequest("publish", CREATOR_PUBLISH_PATH, token, deps, {
|
|
268
|
-
method: "POST",
|
|
269
|
-
body: JSON.stringify({
|
|
270
|
-
...prepareBody,
|
|
271
|
-
operation: "finalize",
|
|
272
|
-
}),
|
|
273
|
-
});
|
|
274
|
-
if (finalized.operation !== "finalize" ||
|
|
275
|
-
typeof finalized.releaseId !== "string" ||
|
|
276
|
-
!UUID.test(finalized.releaseId) ||
|
|
277
|
-
finalized.status !== "pending_review" ||
|
|
278
|
-
finalized.gameId !== projectId ||
|
|
279
|
-
finalized.version !== version ||
|
|
280
|
-
finalized.sha256 !== artifact.sha256 ||
|
|
281
|
-
finalized.sizeBytes !== artifact.sizeBytes) {
|
|
282
|
-
throw new CreatorOperationError("creator_invalid_response", "publish", "The finalize response did not echo the exact project, version, digest, size, release ID, and status.", "Recovery: query summer releases before retrying; the server may have recorded the release even though its response was incomplete.");
|
|
283
|
-
}
|
|
284
|
-
const result = {
|
|
285
|
-
ok: true,
|
|
286
|
-
contract: CREATOR_API_CONTRACT,
|
|
287
|
-
operation: "publish",
|
|
288
|
-
projectId,
|
|
289
|
-
releaseId: finalized.releaseId,
|
|
290
|
-
version,
|
|
291
|
-
sha256: artifact.sha256,
|
|
292
|
-
sizeBytes: artifact.sizeBytes,
|
|
293
|
-
status: finalized.status,
|
|
294
|
-
detail: typeof finalized.detail === "string" ? finalized.detail : null,
|
|
295
|
-
};
|
|
296
|
-
await appendStoreJsonLine("creator-audit.jsonl", {
|
|
297
|
-
...auditBase,
|
|
298
|
-
outcome: "succeeded",
|
|
299
|
-
releaseId: result.releaseId,
|
|
300
|
-
releaseStatus: result.status,
|
|
301
|
-
});
|
|
302
|
-
return result;
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
await appendStoreJsonLine("creator-audit.jsonl", {
|
|
306
|
-
...auditBase,
|
|
307
|
-
outcome: "failed",
|
|
308
|
-
code: error instanceof CreatorOperationError
|
|
309
|
-
? error.code
|
|
310
|
-
: "creator_request_invalid",
|
|
311
|
-
status: error instanceof CreatorOperationError ? error.status ?? null : null,
|
|
312
|
-
});
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
function readReleases(value, projectId) {
|
|
317
|
-
if (!Array.isArray(value)) {
|
|
318
|
-
throw new CreatorOperationError("creator_invalid_response", "releases", "The creator API response did not contain a releases array.", "Recovery: upgrade the Summer CLI and retry; if it repeats, report the creator API as incompatible.");
|
|
319
|
-
}
|
|
320
|
-
const releases = [];
|
|
321
|
-
for (const item of value) {
|
|
322
|
-
const row = item;
|
|
323
|
-
if (!row ||
|
|
324
|
-
typeof row !== "object" ||
|
|
325
|
-
typeof row.releaseId !== "string" ||
|
|
326
|
-
!UUID.test(row.releaseId) ||
|
|
327
|
-
row.gameId !== projectId ||
|
|
328
|
-
typeof row.version !== "string" ||
|
|
329
|
-
!["published", "pending_review", "retired"].includes(String(row.state)) ||
|
|
330
|
-
typeof row.gameStatus !== "string" ||
|
|
331
|
-
(row.sha256 !== null &&
|
|
332
|
-
(typeof row.sha256 !== "string" ||
|
|
333
|
-
!/^[0-9a-f]{64}$/.test(row.sha256))) ||
|
|
334
|
-
(row.sizeBytes !== null &&
|
|
335
|
-
(typeof row.sizeBytes !== "number" ||
|
|
336
|
-
!Number.isSafeInteger(row.sizeBytes) ||
|
|
337
|
-
row.sizeBytes < 0)) ||
|
|
338
|
-
(row.changelog !== null && typeof row.changelog !== "string") ||
|
|
339
|
-
typeof row.submittedAt !== "string" ||
|
|
340
|
-
!Number.isFinite(Date.parse(row.submittedAt)) ||
|
|
341
|
-
(row.artifactPlane !== "r2" && row.artifactPlane !== "legacy")) {
|
|
342
|
-
throw new CreatorOperationError("creator_invalid_response", "releases", "The creator API returned a malformed or cross-project release item.", "Recovery: do not trust this release list; upgrade the Summer CLI or report the creator API as incompatible.");
|
|
343
|
-
}
|
|
344
|
-
releases.push(row);
|
|
345
|
-
}
|
|
346
|
-
return releases;
|
|
347
|
-
}
|
|
348
|
-
export async function listCreatorReleases(input, overrides = {}) {
|
|
349
|
-
const deps = { ...defaultDependencies, ...overrides };
|
|
350
|
-
const projectId = await resolveProjectId(input.projectId);
|
|
351
|
-
const token = await requireCreatorCredential("releases");
|
|
352
|
-
const params = new URLSearchParams({
|
|
353
|
-
gameId: projectId,
|
|
354
|
-
limit: String(input.limit ?? 20),
|
|
355
|
-
});
|
|
356
|
-
if (input.cursor)
|
|
357
|
-
params.set("cursor", input.cursor);
|
|
358
|
-
const payload = await creatorRequest("releases", `${CREATOR_RELEASES_PATH}?${params}`, token, deps);
|
|
359
|
-
if (payload.gameId !== projectId) {
|
|
360
|
-
throw new CreatorOperationError("creator_invalid_response", "releases", "The creator API returned release history for a different project.", "Recovery: do not trust this release list; check creator.apiUrl and report the response as a security issue.");
|
|
361
|
-
}
|
|
362
|
-
if (payload.nextCursor !== null &&
|
|
363
|
-
typeof payload.nextCursor !== "string") {
|
|
364
|
-
throw new CreatorOperationError("creator_invalid_response", "releases", "The creator API returned a malformed release cursor.", "Recovery: omit the cursor and retry from the newest release; report the response if it repeats.");
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
ok: true,
|
|
368
|
-
contract: CREATOR_API_CONTRACT,
|
|
369
|
-
operation: "releases",
|
|
370
|
-
projectId,
|
|
371
|
-
releases: readReleases(payload.releases, projectId),
|
|
372
|
-
nextCursor: payload.nextCursor,
|
|
373
|
-
};
|
|
374
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
id: tool/creator-config
|
|
2
|
-
kind: tool
|
|
3
|
-
version: 1.0.2
|
|
4
|
-
summary: Read or update the shared non-secret Summer configuration; mutations require confirm, and tokens are never returned or accepted.
|
|
5
|
-
use_when:
|
|
6
|
-
- inspecting or changing shared CLI and MCP configuration values
|
|
7
|
-
- '"which creator project id / channel is this CLI pointed at?"'
|
|
8
|
-
- switching the default publish channel
|
|
9
|
-
do_not_use_when:
|
|
10
|
-
- reading or setting auth tokens — they never pass through this tool; use summer login / logout
|
|
11
|
-
facets:
|
|
12
|
-
lifecycle:
|
|
13
|
-
- launch
|
|
14
|
-
domains:
|
|
15
|
-
- creator
|
|
16
|
-
- deployment
|
|
17
|
-
compatibility:
|
|
18
|
-
engine: ">=4.6"
|
|
19
|
-
source: official
|
|
20
|
-
license: MIT
|
|
21
|
-
status: stable
|
|
22
|
-
aliases:
|
|
23
|
-
- summer_creator_config
|
|
24
|
-
implementation:
|
|
25
|
-
module: src/mcp/tools/creator-tools.ts
|
|
26
|
-
export: registerCreatorTools
|
|
27
|
-
surfaces:
|
|
28
|
-
mcp:
|
|
29
|
-
tool_name: summer_creator_config
|
|
30
|
-
remote: false
|
|
31
|
-
cli:
|
|
32
|
-
command: summer config
|
|
33
|
-
input_schema:
|
|
34
|
-
type: object
|
|
35
|
-
properties:
|
|
36
|
-
action:
|
|
37
|
-
type: string
|
|
38
|
-
enum:
|
|
39
|
-
- list
|
|
40
|
-
- get
|
|
41
|
-
- set
|
|
42
|
-
- unset
|
|
43
|
-
key:
|
|
44
|
-
type: string
|
|
45
|
-
enum:
|
|
46
|
-
- gateway.url
|
|
47
|
-
- creator.apiUrl
|
|
48
|
-
- creator.projectId
|
|
49
|
-
- creator.channel
|
|
50
|
-
value:
|
|
51
|
-
type: string
|
|
52
|
-
confirm:
|
|
53
|
-
type: boolean
|
|
54
|
-
default: false
|
|
55
|
-
required:
|
|
56
|
-
- action
|
|
57
|
-
additionalProperties: false
|
|
58
|
-
authority:
|
|
59
|
-
filesystem: true
|
|
60
|
-
editor_mutation: false
|
|
61
|
-
network: false
|
|
62
|
-
credentials: false
|
|
63
|
-
publish: false
|