summer-engine 1.5.0 → 2.0.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/plugin.json +18 -10
- package/.codex-plugin/plugin.json +36 -8
- package/.cursor-plugin/plugin.json +19 -11
- package/.opencode/INSTALL.md +69 -0
- package/.opencode/plugins/summer.js +9 -4
- package/CHANGELOG.md +31 -1
- package/README.md +268 -86
- package/dist/bin/summer.js +0 -0
- package/dist/commands/assets.d.ts +2 -0
- package/dist/commands/assets.js +189 -0
- package/dist/commands/create.js +55 -17
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +127 -0
- package/dist/commands/list.js +24 -5
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +170 -0
- package/dist/commands/scene.d.ts +2 -0
- package/dist/commands/scene.js +290 -0
- package/dist/lib/banner.js +2 -2
- package/dist/lib/log-filters.d.ts +53 -0
- package/dist/lib/log-filters.js +130 -0
- package/dist/lib/plugin-manifests.test.d.ts +1 -0
- package/dist/lib/plugin-manifests.test.js +134 -0
- package/dist/lib/remote-templates.d.ts +25 -0
- package/dist/lib/remote-templates.js +86 -0
- package/dist/lib/skills-registry.d.ts +17 -1
- package/dist/lib/skills-registry.js +23 -1
- package/dist/lib/telemetry.d.ts +13 -0
- package/dist/lib/telemetry.js +63 -0
- package/dist/lib/with-engine.d.ts +14 -0
- package/dist/lib/with-engine.js +109 -0
- package/dist/mcp/tools/asset-tools.js +12 -13
- package/dist/mcp/tools/debug-tools.js +32 -9
- package/dist/mcp/tools/with-engine.js +4 -0
- package/gemini-extension.json +2 -2
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/marketplace.json +2 -2
- package/package.json +3 -3
- package/skills/README.md +1 -1
- package/skills/_meta/skill-create/SKILL.md +1 -1
- package/skills/_meta/skill-improve/SKILL.md +1 -1
- package/skills/_meta/skill-test/SKILL.md +1 -1
- package/skills/_meta/using-summer/SKILL.md +130 -0
- package/skills/asset-pipeline/asset-strategy/SKILL.md +1 -1
- package/skills/catalog.yaml +17 -2
- package/skills/character-controllers/fps-controller/SKILL.md +1 -1
- package/skills/debugging/debug/SKILL.md +102 -82
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/scene-and-project/browse-templates/SKILL.md +105 -0
- package/skills/scene-and-project/make-game/SKILL.md +159 -346
- package/skills/scene-and-project/new-project/SKILL.md +106 -0
- package/skills/scene-and-project/scene-composition/SKILL.md +1 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discover and clone open-source Summer Engine project templates hosted at
|
|
3
|
+
* github.com/SummerEngine/template-*.
|
|
4
|
+
*
|
|
5
|
+
* The GitHub API call is cached for one hour in memory to avoid rate-limit
|
|
6
|
+
* pain when an agent calls back-to-back.
|
|
7
|
+
*/
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
const ORG = "SummerEngine";
|
|
11
|
+
const TEMPLATE_PREFIX = "template-";
|
|
12
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
13
|
+
let cache = null;
|
|
14
|
+
export async function fetchRemoteTemplates(opts = {}) {
|
|
15
|
+
if (!opts.force && cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
|
|
16
|
+
return cache.templates;
|
|
17
|
+
}
|
|
18
|
+
const url = `https://api.github.com/orgs/${ORG}/repos?per_page=100&type=public`;
|
|
19
|
+
const headers = {
|
|
20
|
+
Accept: "application/vnd.github+json",
|
|
21
|
+
"User-Agent": "summer-engine-cli",
|
|
22
|
+
};
|
|
23
|
+
if (process.env.GITHUB_TOKEN)
|
|
24
|
+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
25
|
+
const res = await fetch(url, { headers });
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
throw new Error(`GitHub API ${res.status}: ${res.statusText}. ${res.status === 403 ? "Rate-limited; set GITHUB_TOKEN to raise the cap." : ""}`);
|
|
28
|
+
}
|
|
29
|
+
const repos = (await res.json());
|
|
30
|
+
const templates = repos
|
|
31
|
+
.filter((r) => r.name.startsWith(TEMPLATE_PREFIX) && !r.archived && !r.fork)
|
|
32
|
+
.map((r) => ({
|
|
33
|
+
slug: r.name.slice(TEMPLATE_PREFIX.length),
|
|
34
|
+
repo: r.name,
|
|
35
|
+
description: r.description ?? "",
|
|
36
|
+
branch: r.default_branch,
|
|
37
|
+
url: r.html_url,
|
|
38
|
+
cloneUrl: r.clone_url,
|
|
39
|
+
stars: r.stargazers_count,
|
|
40
|
+
updatedAt: r.updated_at,
|
|
41
|
+
}))
|
|
42
|
+
.sort((a, b) => b.stars - a.stars || a.slug.localeCompare(b.slug));
|
|
43
|
+
cache = { templates, fetchedAt: Date.now() };
|
|
44
|
+
return templates;
|
|
45
|
+
}
|
|
46
|
+
export function cloneTemplate(template, opts) {
|
|
47
|
+
if (existsSync(opts.targetDir)) {
|
|
48
|
+
throw new Error(`Directory already exists: ${opts.targetDir}`);
|
|
49
|
+
}
|
|
50
|
+
// Shallow clone for speed.
|
|
51
|
+
try {
|
|
52
|
+
execSync(`git clone --depth 1 --branch ${template.branch} ${template.cloneUrl} "${opts.targetDir}"`, {
|
|
53
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new Error(`git clone failed for ${template.repo}. Is git installed? ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
if (opts.detach !== false) {
|
|
60
|
+
// Default to detached: the user wants their own history.
|
|
61
|
+
const gitDir = `${opts.targetDir}/.git`;
|
|
62
|
+
if (existsSync(gitDir)) {
|
|
63
|
+
try {
|
|
64
|
+
execSync(process.platform === "win32" ? `rmdir /s /q "${gitDir}"` : `rm -rf "${gitDir}"`, {
|
|
65
|
+
stdio: "ignore",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// best effort — user can clean up manually if it fails
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Quick match: exact slug, then prefix, then substring. Returns null if ambiguous or none. */
|
|
75
|
+
export function matchTemplate(query, templates) {
|
|
76
|
+
const exact = templates.find((t) => t.slug === query || t.repo === query);
|
|
77
|
+
if (exact)
|
|
78
|
+
return exact;
|
|
79
|
+
const prefix = templates.filter((t) => t.slug.startsWith(query));
|
|
80
|
+
if (prefix.length === 1)
|
|
81
|
+
return prefix[0];
|
|
82
|
+
const substring = templates.filter((t) => t.slug.includes(query));
|
|
83
|
+
if (substring.length === 1)
|
|
84
|
+
return substring[0];
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
@@ -85,7 +85,7 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
85
85
|
readonly category: "scene-and-project";
|
|
86
86
|
readonly public: true;
|
|
87
87
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
88
|
-
readonly recommended:
|
|
88
|
+
readonly recommended: true;
|
|
89
89
|
readonly requiresMcpTools: readonly ["summer_get_agent_playbook", "summer_get_project_context", "summer_create_scene", "summer_open_scene", "summer_save_scene", "summer_add_node", "summer_set_prop", "summer_set_resource_property", "summer_instantiate_scene", "summer_play", "summer_stop", "summer_get_diagnostics"];
|
|
90
90
|
readonly testScenario: "Build one narrow playable prototype from requirements through scene setup, host-edited scripts, and diagnostics.";
|
|
91
91
|
}, {
|
|
@@ -96,6 +96,22 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
96
96
|
readonly recommended: true;
|
|
97
97
|
readonly requiresMcpTools: readonly ["summer_get_project_context", "summer_get_scene_tree"];
|
|
98
98
|
readonly testScenario: "Walk a fresh user from \"I want to make a game but I don't know what\" to a 1-page brief saved at .summer/GameSoul.md.";
|
|
99
|
+
}, {
|
|
100
|
+
readonly name: "new-project";
|
|
101
|
+
readonly category: "scene-and-project";
|
|
102
|
+
readonly public: true;
|
|
103
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
104
|
+
readonly recommended: true;
|
|
105
|
+
readonly requiresMcpTools: readonly ["summer_get_project_context"];
|
|
106
|
+
readonly testScenario: "User says \"start a blank project\" — skill asks for the name, picks empty vs 3d-basic, runs summer create, opens the engine, and stops to ask what to build.";
|
|
107
|
+
}, {
|
|
108
|
+
readonly name: "browse-templates";
|
|
109
|
+
readonly category: "scene-and-project";
|
|
110
|
+
readonly public: true;
|
|
111
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
112
|
+
readonly recommended: true;
|
|
113
|
+
readonly requiresMcpTools: readonly ["summer_get_project_context"];
|
|
114
|
+
readonly testScenario: "User says \"what templates exist?\" — skill runs summer list templates, presents 3-5 curated picks, asks the project name, and runs summer create <slug> <name>.";
|
|
99
115
|
}, {
|
|
100
116
|
readonly name: "design-mechanic";
|
|
101
117
|
readonly category: "gameplay-mechanics";
|
|
@@ -158,7 +158,7 @@ export const SKILL_REGISTRY = [
|
|
|
158
158
|
category: "scene-and-project",
|
|
159
159
|
public: true,
|
|
160
160
|
clients: ALL_CLIENTS,
|
|
161
|
-
recommended:
|
|
161
|
+
recommended: true,
|
|
162
162
|
requiresMcpTools: [
|
|
163
163
|
"summer_get_agent_playbook",
|
|
164
164
|
"summer_get_project_context",
|
|
@@ -187,6 +187,28 @@ export const SKILL_REGISTRY = [
|
|
|
187
187
|
],
|
|
188
188
|
testScenario: "Walk a fresh user from \"I want to make a game but I don't know what\" to a 1-page brief saved at .summer/GameSoul.md.",
|
|
189
189
|
},
|
|
190
|
+
{
|
|
191
|
+
name: "new-project",
|
|
192
|
+
category: "scene-and-project",
|
|
193
|
+
public: true,
|
|
194
|
+
clients: ALL_CLIENTS,
|
|
195
|
+
recommended: true,
|
|
196
|
+
requiresMcpTools: [
|
|
197
|
+
"summer_get_project_context",
|
|
198
|
+
],
|
|
199
|
+
testScenario: "User says \"start a blank project\" — skill asks for the name, picks empty vs 3d-basic, runs summer create, opens the engine, and stops to ask what to build.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "browse-templates",
|
|
203
|
+
category: "scene-and-project",
|
|
204
|
+
public: true,
|
|
205
|
+
clients: ALL_CLIENTS,
|
|
206
|
+
recommended: true,
|
|
207
|
+
requiresMcpTools: [
|
|
208
|
+
"summer_get_project_context",
|
|
209
|
+
],
|
|
210
|
+
testScenario: "User says \"what templates exist?\" — skill runs summer list templates, presents 3-5 curated picks, asks the project name, and runs summer create <slug> <name>.",
|
|
211
|
+
},
|
|
190
212
|
{
|
|
191
213
|
name: "design-mechanic",
|
|
192
214
|
category: "gameplay-mechanics",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ping the telemetry endpoint once per MCP server lifetime.
|
|
3
|
+
*
|
|
4
|
+
* - No await: returns synchronously after kicking off the request.
|
|
5
|
+
* - No throw: every error is swallowed.
|
|
6
|
+
* - No retry: a single best-effort attempt.
|
|
7
|
+
* - Skips entirely if no auth token is on disk (anonymous users still work).
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordMcpSession(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Reset the once-per-session guard. Test-only.
|
|
12
|
+
*/
|
|
13
|
+
export declare function _resetTelemetryPingedForTests(): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-telemetry beacon for MCP usage. Fire-and-forget, never blocks, never throws.
|
|
3
|
+
* One ping per MCP server lifetime — counts daily-active users, not per-tool calls.
|
|
4
|
+
*
|
|
5
|
+
* The endpoint /api/mcp/log-local-call always returns allowed:true and never gates.
|
|
6
|
+
* No quota. The user account is the only thing being attributed; the response is
|
|
7
|
+
* ignored entirely on the CLI side.
|
|
8
|
+
*/
|
|
9
|
+
import { getAuthToken } from "./auth.js";
|
|
10
|
+
const TELEMETRY_URL = process.env.SUMMER_GATEWAY_URL?.replace(/\/+$/, "") ?? "https://www.summerengine.com";
|
|
11
|
+
const TELEMETRY_PATH = "/api/mcp/log-local-call";
|
|
12
|
+
const TIMEOUT_MS = 5000;
|
|
13
|
+
let pinged = false;
|
|
14
|
+
/**
|
|
15
|
+
* Ping the telemetry endpoint once per MCP server lifetime.
|
|
16
|
+
*
|
|
17
|
+
* - No await: returns synchronously after kicking off the request.
|
|
18
|
+
* - No throw: every error is swallowed.
|
|
19
|
+
* - No retry: a single best-effort attempt.
|
|
20
|
+
* - Skips entirely if no auth token is on disk (anonymous users still work).
|
|
21
|
+
*/
|
|
22
|
+
export function recordMcpSession() {
|
|
23
|
+
if (pinged)
|
|
24
|
+
return;
|
|
25
|
+
pinged = true;
|
|
26
|
+
// Wrap the entire async pipeline in a void-discarded promise so the caller
|
|
27
|
+
// never accidentally awaits us.
|
|
28
|
+
void (async () => {
|
|
29
|
+
try {
|
|
30
|
+
const token = await getAuthToken();
|
|
31
|
+
if (!token)
|
|
32
|
+
return; // unauthenticated MCP usage is allowed; we just can't attribute it
|
|
33
|
+
const ctrl = new AbortController();
|
|
34
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
35
|
+
try {
|
|
36
|
+
await fetch(`${TELEMETRY_URL}${TELEMETRY_PATH}`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
signal: ctrl.signal,
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${token}`,
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
client: "summer-cli",
|
|
45
|
+
event: "mcp_session_start",
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// never throws — telemetry is best-effort
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reset the once-per-session guard. Test-only.
|
|
60
|
+
*/
|
|
61
|
+
export function _resetTelemetryPingedForTests() {
|
|
62
|
+
pinged = false;
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EngineApiClient } from "./api-client.js";
|
|
2
|
+
export declare function extractOpError(result: unknown): string | null;
|
|
3
|
+
export declare function buildActionHint(message: string): string | null;
|
|
4
|
+
export declare function checkQuota(): Promise<{
|
|
5
|
+
allowed: boolean;
|
|
6
|
+
message?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function getClient(): Promise<EngineApiClient>;
|
|
9
|
+
export declare function resetClient(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Connect to the engine, check quota, and run the given function.
|
|
12
|
+
* Throws on errors (quota, connection, op errors).
|
|
13
|
+
*/
|
|
14
|
+
export declare function withEngineClient<T>(fn: (client: EngineApiClient) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { EngineApiClient } from "./api-client.js";
|
|
2
|
+
import { getAuthToken } from "./auth.js";
|
|
3
|
+
const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
|
|
4
|
+
export function extractOpError(result) {
|
|
5
|
+
if (!result || typeof result !== "object")
|
|
6
|
+
return null;
|
|
7
|
+
const op = result;
|
|
8
|
+
if (op.status === "error" && op.error)
|
|
9
|
+
return op.error;
|
|
10
|
+
const firstFailed = op.results?.find((r) => r.ok === false && r.error);
|
|
11
|
+
return firstFailed?.error ?? null;
|
|
12
|
+
}
|
|
13
|
+
export function buildActionHint(message) {
|
|
14
|
+
const normalized = message.toLowerCase();
|
|
15
|
+
if (normalized.includes("no scene open") || normalized.includes("no edited scene")) {
|
|
16
|
+
return "No scene is currently open. Run `summer project context` first, then `summer project open-main` (or `summer scene open` with a known .tscn path).";
|
|
17
|
+
}
|
|
18
|
+
if (normalized.includes("failed to open scene")) {
|
|
19
|
+
return "Scene path could not be opened. Run `summer project context` to get mainScene, then open that exact path. Avoid guessing scene filenames.";
|
|
20
|
+
}
|
|
21
|
+
if (normalized.includes("writefile cannot edit .tscn/.scn")) {
|
|
22
|
+
return "Cannot write .tscn/.scn directly. Use summer scene commands for scene-tree operations, and file-edit tools for .gd scripts.";
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export async function checkQuota() {
|
|
27
|
+
const token = await getAuthToken();
|
|
28
|
+
if (!token) {
|
|
29
|
+
return {
|
|
30
|
+
allowed: false,
|
|
31
|
+
message: "Not signed in. Run: summer login\nOr open: https://www.summerengine.com/login\nEngine tools require a Summer Engine account (free to create).",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`${GATEWAY_URL}/api/mcp/log-local-call`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Bearer ${token}`,
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({}),
|
|
42
|
+
signal: AbortSignal.timeout(10000),
|
|
43
|
+
});
|
|
44
|
+
const data = (await res.json().catch(() => ({})));
|
|
45
|
+
if ([401, 402, 403, 429].includes(res.status)) {
|
|
46
|
+
return {
|
|
47
|
+
allowed: false,
|
|
48
|
+
message: data.message || "Quota check failed. Try again.",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Soft-fail on service/gateway issues so local tools keep working.
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
return { allowed: true };
|
|
54
|
+
}
|
|
55
|
+
if (data.allowed === false) {
|
|
56
|
+
return {
|
|
57
|
+
allowed: false,
|
|
58
|
+
message: data.message || "Call limit reached. Upgrade at https://summerengine.com/pricing",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { allowed: true };
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Network/transient errors should not brick local usage.
|
|
65
|
+
return { allowed: true };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let cachedClient = null;
|
|
69
|
+
export function getClient() {
|
|
70
|
+
if (cachedClient)
|
|
71
|
+
return Promise.resolve(cachedClient);
|
|
72
|
+
return EngineApiClient.connect().then((c) => {
|
|
73
|
+
cachedClient = c;
|
|
74
|
+
return c;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function resetClient() {
|
|
78
|
+
cachedClient = null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Connect to the engine, check quota, and run the given function.
|
|
82
|
+
* Throws on errors (quota, connection, op errors).
|
|
83
|
+
*/
|
|
84
|
+
export async function withEngineClient(fn) {
|
|
85
|
+
const quota = await checkQuota();
|
|
86
|
+
if (!quota.allowed) {
|
|
87
|
+
throw new Error(quota.message || "Quota exceeded");
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const client = await getClient();
|
|
91
|
+
const result = await fn(client);
|
|
92
|
+
const opError = extractOpError(result);
|
|
93
|
+
if (opError) {
|
|
94
|
+
const hint = buildActionHint(opError);
|
|
95
|
+
throw new Error(hint ? `${opError}\n\nHint: ${hint}` : opError);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Reset client on connection errors so next call retries
|
|
101
|
+
if (err instanceof Error &&
|
|
102
|
+
(err.message.includes("not running") ||
|
|
103
|
+
err.message.includes("not responding") ||
|
|
104
|
+
err.message.includes("ECONNREFUSED"))) {
|
|
105
|
+
resetClient();
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -65,14 +65,6 @@ async function searchAssetsApi(params) {
|
|
|
65
65
|
});
|
|
66
66
|
const data = (await res.json());
|
|
67
67
|
if (!res.ok) {
|
|
68
|
-
if (res.status === 402) {
|
|
69
|
-
return {
|
|
70
|
-
error: "upgrade_required",
|
|
71
|
-
message: (data.message || "Asset search requires a Pro subscription.") +
|
|
72
|
-
"\nUpgrade at: https://www.summerengine.com/pricing\nFree tier includes all other MCP tools (scene editing, debugging, etc).",
|
|
73
|
-
plan: data.plan,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
68
|
if (res.status === 401) {
|
|
77
69
|
return {
|
|
78
70
|
error: "unauthorized",
|
|
@@ -80,6 +72,13 @@ async function searchAssetsApi(params) {
|
|
|
80
72
|
" The user needs to re-authenticate:\n npx summer-engine login --force",
|
|
81
73
|
};
|
|
82
74
|
}
|
|
75
|
+
if (res.status === 429) {
|
|
76
|
+
return {
|
|
77
|
+
error: "rate_limited",
|
|
78
|
+
message: (data.message || "Asset search rate limit hit. Wait a moment and try again.") +
|
|
79
|
+
" The public library is free, but rate-limited per user to prevent bulk scraping.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
83
82
|
return {
|
|
84
83
|
error: data.error || "Search failed",
|
|
85
84
|
message: data.message || "Asset search failed",
|
|
@@ -88,17 +87,17 @@ async function searchAssetsApi(params) {
|
|
|
88
87
|
return data;
|
|
89
88
|
}
|
|
90
89
|
export function registerAssetTools(server) {
|
|
91
|
-
server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem.
|
|
90
|
+
server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem. **Free for all users** with rate limits.
|
|
92
91
|
|
|
93
92
|
Sources:
|
|
94
|
-
- "library" (default) — Public asset library (25k+ community assets).
|
|
95
|
-
- "my_assets" — Your own generated/uploaded assets. Free
|
|
93
|
+
- "library" (default) — Public asset library (25k+ community assets). Free.
|
|
94
|
+
- "my_assets" — Your own generated/uploaded assets. Free. Query is optional.
|
|
96
95
|
- "all" — Search both library and your assets.
|
|
97
96
|
|
|
98
97
|
Uses hybrid search: keywords + semantic similarity. Finds assets by name AND by meaning.
|
|
99
98
|
Returns asset names, types, preview URLs, and import-ready file URLs.
|
|
100
99
|
|
|
101
|
-
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
100
|
+
Requires authentication (so we can attribute usage and apply per-user rate limits): run 'npx summer-engine login' first.`, {
|
|
102
101
|
query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon'. For my_assets, can be empty to list recent."),
|
|
103
102
|
assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("all").describe("Filter by asset type"),
|
|
104
103
|
limit: z.number().default(10).describe("Max results (1-20)"),
|
|
@@ -110,7 +109,7 @@ Requires authentication: run 'npx summer-engine login' first.`, {
|
|
|
110
109
|
content: [
|
|
111
110
|
{
|
|
112
111
|
type: "text",
|
|
113
|
-
text: JSON.stringify({ error: result.error, message: result.message
|
|
112
|
+
text: JSON.stringify({ error: result.error, message: result.message }, null, 2),
|
|
114
113
|
},
|
|
115
114
|
],
|
|
116
115
|
isError: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { withEngine } from "./with-engine.js";
|
|
3
|
+
import { shapeEngineLogResponse } from "../../lib/log-filters.js";
|
|
3
4
|
export function registerDebugTools(server) {
|
|
4
5
|
server.tool("summer_get_diagnostics", `Quick overview of all errors and warnings from both the editor console and the runtime debugger. Returns error counts and a guidance message.
|
|
5
6
|
|
|
@@ -10,27 +11,49 @@ Typical workflow after making changes:
|
|
|
10
11
|
2. If errors: summer_get_console or summer_get_debugger_errors for details
|
|
11
12
|
3. Fix the issues
|
|
12
13
|
4. summer_get_diagnostics again to verify`, {}, async () => withEngine(async (client) => client.getDiagnostics()));
|
|
13
|
-
server.tool("summer_get_console",
|
|
14
|
-
|
|
14
|
+
server.tool("summer_get_console", `Read recent messages from the editor's Output panel.
|
|
15
|
+
|
|
16
|
+
Output is post-processed for token economy: consecutive identical messages collapse into one entry with a "(×N)" count suffix, and the response carries a "_filter" summary so you can see what was hidden. Use errors_only=true (default) to drop info/std noise; use raw=true to bypass all shaping.
|
|
17
|
+
|
|
18
|
+
Use after summer_get_diagnostics indicates console issues.`, {
|
|
19
|
+
max_lines: z.number().optional().default(100).describe("Max lines to return after dedupe (default 100)"),
|
|
15
20
|
filter: z.string().optional().describe("Only return lines containing this string"),
|
|
16
|
-
type: z.enum(["error", "warning", "std", "editor"]).optional().describe("Filter by message type"),
|
|
17
|
-
|
|
21
|
+
type: z.enum(["error", "warning", "std", "editor"]).optional().describe("Filter by message type at the engine level"),
|
|
22
|
+
errors_only: z.boolean().optional().default(true).describe("Drop info/std noise, keep errors and warnings (default true)"),
|
|
23
|
+
strict_errors: z.boolean().optional().default(false).describe("Drop warnings too — return errors only"),
|
|
24
|
+
raw: z.boolean().optional().default(false).describe("Bypass dedupe and level filtering — return engine output verbatim"),
|
|
25
|
+
}, async ({ max_lines, filter, type, errors_only, strict_errors, raw }) => withEngine(async (client) => {
|
|
18
26
|
const op = { op: "GetConsoleOutput", max_lines };
|
|
19
27
|
if (filter)
|
|
20
28
|
op.filter = filter;
|
|
21
29
|
if (type)
|
|
22
30
|
op.type = type;
|
|
23
|
-
|
|
31
|
+
const engineResult = await client.executeOps([op]);
|
|
32
|
+
if (raw)
|
|
33
|
+
return engineResult;
|
|
34
|
+
const { result } = shapeEngineLogResponse(engineResult, {
|
|
35
|
+
errorsOnly: errors_only,
|
|
36
|
+
errorsOnlyStrict: strict_errors,
|
|
37
|
+
maxEntries: max_lines,
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
24
40
|
}));
|
|
25
41
|
server.tool("summer_clear_console", "Clear the editor's Output panel. Useful before running the game to get a clean slate for error checking.", {}, async () => withEngine(async (client) => client.executeOps([{ op: "ClearConsoleOutput" }])));
|
|
26
|
-
server.tool("summer_get_debugger_errors",
|
|
27
|
-
|
|
42
|
+
server.tool("summer_get_debugger_errors", `Read runtime errors and warnings from the debugger. These are errors that occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.
|
|
43
|
+
|
|
44
|
+
Output is deduped: identical errors firing every frame collapse into one entry with a "(×N)" count suffix. A "_filter" summary tells you exactly what was collapsed or truncated. Use raw=true to bypass shaping when you really need every entry.`, {
|
|
45
|
+
max_errors: z.number().optional().default(50).describe("Max errors to return after dedupe"),
|
|
28
46
|
include_stack: z.boolean().optional().describe("Include stack traces for each error"),
|
|
29
|
-
|
|
47
|
+
raw: z.boolean().optional().default(false).describe("Bypass dedupe — return engine output verbatim"),
|
|
48
|
+
}, async ({ max_errors, include_stack, raw }) => withEngine(async (client) => {
|
|
30
49
|
const op = { op: "GetDebuggerErrors", max_errors };
|
|
31
50
|
if (include_stack !== undefined)
|
|
32
51
|
op.include_stack = include_stack;
|
|
33
|
-
|
|
52
|
+
const engineResult = await client.executeOps([op]);
|
|
53
|
+
if (raw)
|
|
54
|
+
return engineResult;
|
|
55
|
+
const { result } = shapeEngineLogResponse(engineResult, { maxEntries: max_errors });
|
|
56
|
+
return result;
|
|
34
57
|
}));
|
|
35
58
|
server.tool("summer_play", `Start running the game in the engine. The game runs inside Summer Engine's viewport.
|
|
36
59
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getClient, resetClient } from "../server.js";
|
|
2
|
+
import { recordMcpSession } from "../../lib/telemetry.js";
|
|
2
3
|
function extractOpError(result) {
|
|
3
4
|
if (!result || typeof result !== "object")
|
|
4
5
|
return null;
|
|
@@ -24,6 +25,9 @@ function buildActionHint(message) {
|
|
|
24
25
|
return null;
|
|
25
26
|
}
|
|
26
27
|
export async function withEngine(fn) {
|
|
28
|
+
// Best-effort, fire-and-forget: count this MCP session as DAU for attribution.
|
|
29
|
+
// No await, no throw, no quota gating.
|
|
30
|
+
recordMcpSession();
|
|
27
31
|
try {
|
|
28
32
|
const client = await getClient();
|
|
29
33
|
const result = await fn(client);
|
package/gemini-extension.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "summer",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI game
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Superpowers for AI game dev. MCP bridge to the local Summer Engine plus a context primer that teaches Gemini the 22 Summer skills. Use with the npm CLI for skill files on disk.",
|
|
5
5
|
"contextFileName": "GEMINI.md",
|
|
6
6
|
"mcpServers": {
|
|
7
7
|
"summer-engine": {
|
|
File without changes
|
package/hooks/run-hook.cmd
CHANGED
|
File without changes
|
package/hooks/session-start.sh
CHANGED
|
File without changes
|
package/marketplace.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
{
|
|
8
8
|
"name": "summer",
|
|
9
9
|
"source": "./",
|
|
10
|
-
"version": "
|
|
11
|
-
"description": "AI game
|
|
10
|
+
"version": "2.0.0",
|
|
11
|
+
"description": "Superpowers for AI game dev. 24 auto-trigger skills under summer: namespace, MCP bridge to the local Summer Engine, session-start + pre-commit hooks.",
|
|
12
12
|
"author": { "name": "Summer Engine", "email": "founders@summerengine.com" },
|
|
13
13
|
"category": "game-development",
|
|
14
14
|
"tags": ["game-dev", "godot", "ai-game-engine"],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "summer-engine",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI and MCP tools for Summer Engine
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Summer — superpowers for AI game dev. CLI, skills, and MCP tools for Summer Engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"game-engine",
|
|
7
7
|
"godot",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"homepage": "https://summerengine.com",
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "https://github.com/SummerEngine/summer
|
|
17
|
+
"url": "https://github.com/SummerEngine/summer"
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"author": "Summer Engine <founders@summerengine.com>",
|
package/skills/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The canonical agent-readable game-dev knowledge source for Claude Code, Cursor, Codex, and Windsurf.
|
|
4
4
|
|
|
5
|
-
When an agent is asked to make a game in
|
|
5
|
+
When an agent is asked to make a game in Summer Engine, this is what it reads to learn how.
|
|
6
6
|
|
|
7
7
|
## Layout
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-create
|
|
3
|
-
description:
|
|
3
|
+
description: Use when a contributor wants to add a new skill to the Summer library — bootstraps the canonical folder structure, frontmatter, and stub sections. Trigger on "create skill", "add skill", "new skill", "scaffold a skill".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: _meta
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-improve
|
|
3
|
-
description:
|
|
3
|
+
description: Use when a contributor wants to upgrade a Summer skill that is underperforming or to fix a regression — runs the skill against a behavioral spec with and without proposed changes via parallel-eval harness and ships the version that wins. Trigger on "improve skill", "iterate on skill", "make this skill better".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: _meta
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-test
|
|
3
|
-
description:
|
|
3
|
+
description: Use when a contributor wants to lint a Summer skill before commit, audit the whole library, or check a behavioral spec assertion — validates against static structural rules and per-skill behavioral specs. Trigger on "skill test", "lint skill", "validate skill".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: _meta
|