summer-engine 2.6.5 → 2.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 +1 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +5 -2
- package/.cursor-plugin/plugin.json +4 -1
- package/.opencode/plugins/summer.js +1 -1
- package/AGENTS.md +8 -4
- package/CHANGELOG.md +41 -0
- package/GEMINI.md +5 -4
- package/README.md +25 -20
- package/_persona/summer/SKILL.md +1 -0
- package/commands/summer.md +13 -0
- package/dist/bin/summer.js +4 -0
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +44 -0
- package/dist/commands/doctor.js +112 -8
- package/dist/commands/skills.js +1 -1
- package/dist/lib/api-client.d.ts +22 -6
- package/dist/lib/api-client.js +161 -36
- package/dist/lib/async-op-lifecycle.d.ts +7 -2
- package/dist/lib/async-op-lifecycle.js +32 -9
- package/dist/lib/cloud/engine-bridge.js +9 -1
- package/dist/lib/debug-report.d.ts +50 -0
- package/dist/lib/debug-report.js +308 -0
- package/dist/lib/game-task-plan.js +4 -4
- package/dist/lib/mcp-boot-notice.d.ts +3 -0
- package/dist/lib/mcp-boot-notice.js +7 -0
- package/dist/lib/mcp-log.d.ts +3 -0
- package/dist/lib/mcp-log.js +72 -0
- package/dist/lib/skills-registry.d.ts +25 -1
- package/dist/lib/skills-registry.js +46 -0
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +79 -7
- package/dist/mcp/tools/asset-tools.js +33 -12
- package/dist/mcp/tools/cloud-tools.js +15 -15
- package/dist/mcp/tools/debug-tools.js +44 -1
- package/dist/mcp/tools/file-tools.d.ts +2 -0
- package/dist/mcp/tools/file-tools.js +154 -0
- package/dist/mcp/tools/generate-tools.js +143 -5
- package/dist/mcp/tools/project-tools.js +25 -20
- package/dist/mcp/tools/scene-tools.js +101 -24
- package/dist/mcp/tools/with-engine.js +4 -4
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +6 -2
- package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
- package/skills/debugging/debug/SKILL.md +23 -1
- package/skills/deployment/remote-deploy/SKILL.md +107 -0
- package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
- package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
- package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
- package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
- package/skills/workflow/mcpupdate/SKILL.md +6 -6
- package/skills/workflow/using-summer/SKILL.md +20 -1
- package/skills/workflow/writing-skills/render-graphs.js +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { hostname, platform, release } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { EngineApiClient } from "./api-client.js";
|
|
6
|
+
import { shapeEngineLogResponse } from "./log-filters.js";
|
|
7
|
+
import { tildeify } from "./format.js";
|
|
8
|
+
import { getMcpLogPath, readRecentMcpLogLines } from "./mcp-log.js";
|
|
9
|
+
import { runDoctor } from "../commands/doctor.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require("../../package.json");
|
|
12
|
+
function sleep(ms) {
|
|
13
|
+
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
14
|
+
}
|
|
15
|
+
function errorMessage(error) {
|
|
16
|
+
return error instanceof Error ? error.message : String(error);
|
|
17
|
+
}
|
|
18
|
+
function timestampForFile(date) {
|
|
19
|
+
return date.toISOString().replace(/[:.]/g, "-");
|
|
20
|
+
}
|
|
21
|
+
export function defaultDebugReportFilename(date = new Date()) {
|
|
22
|
+
return `summer-debug-report-${timestampForFile(date)}.md`;
|
|
23
|
+
}
|
|
24
|
+
function asRecord(value) {
|
|
25
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
function pickString(value, keys) {
|
|
30
|
+
const record = asRecord(value);
|
|
31
|
+
if (!record)
|
|
32
|
+
return null;
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const candidate = record[key];
|
|
35
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function projectPathFromReport(report) {
|
|
41
|
+
return pickString(report.engine.health, ["project_path", "projectPath", "projectRoot", "project_root"]);
|
|
42
|
+
}
|
|
43
|
+
function resolveOutputPath(report, options) {
|
|
44
|
+
if (options.outputPath) {
|
|
45
|
+
return isAbsolute(options.outputPath)
|
|
46
|
+
? options.outputPath
|
|
47
|
+
: resolve(process.cwd(), options.outputPath);
|
|
48
|
+
}
|
|
49
|
+
const baseDir = options.baseDir ?? projectPathFromReport(report) ?? process.cwd();
|
|
50
|
+
return join(baseDir, defaultDebugReportFilename(new Date(report.generatedAt)));
|
|
51
|
+
}
|
|
52
|
+
async function writeMarkdown(path, markdown) {
|
|
53
|
+
await mkdir(dirname(path), { recursive: true });
|
|
54
|
+
await writeFile(path, markdown, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
async function safeCollect(label, fn) {
|
|
57
|
+
try {
|
|
58
|
+
return await fn();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
label,
|
|
64
|
+
error: errorMessage(error),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function firstOpsResult(value) {
|
|
69
|
+
const root = asRecord(value);
|
|
70
|
+
const results = root?.results;
|
|
71
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
72
|
+
return asRecord(results[0]);
|
|
73
|
+
}
|
|
74
|
+
return root;
|
|
75
|
+
}
|
|
76
|
+
function isGamePlaying(value) {
|
|
77
|
+
return firstOpsResult(value)?.playing === true;
|
|
78
|
+
}
|
|
79
|
+
async function collectConsole(client, maxConsoleLines) {
|
|
80
|
+
const raw = await client.executeOps([
|
|
81
|
+
{ op: "GetConsoleOutput", max_lines: maxConsoleLines },
|
|
82
|
+
]);
|
|
83
|
+
return shapeEngineLogResponse(raw, {
|
|
84
|
+
errorsOnly: false,
|
|
85
|
+
maxEntries: maxConsoleLines,
|
|
86
|
+
}).result;
|
|
87
|
+
}
|
|
88
|
+
async function collectDebugger(client, type, maxEntries) {
|
|
89
|
+
const op = {
|
|
90
|
+
op: "GetDebuggerErrors",
|
|
91
|
+
max_errors: maxEntries,
|
|
92
|
+
include_stack: true,
|
|
93
|
+
};
|
|
94
|
+
if (type === "warning")
|
|
95
|
+
op.type = "warning";
|
|
96
|
+
const raw = await client.executeOps([op]);
|
|
97
|
+
return shapeEngineLogResponse(raw, { maxEntries }).result;
|
|
98
|
+
}
|
|
99
|
+
async function collectPlaySession(client, options) {
|
|
100
|
+
const session = {
|
|
101
|
+
requested: true,
|
|
102
|
+
waitMs: options.playWaitMs,
|
|
103
|
+
};
|
|
104
|
+
session.before = await safeCollect("is-running-before", () => client.executeOps([{ op: "IsGameRunning" }]));
|
|
105
|
+
const wasPlaying = isGamePlaying(session.before);
|
|
106
|
+
session.wasPlayingBefore = wasPlaying;
|
|
107
|
+
if (wasPlaying) {
|
|
108
|
+
session.note =
|
|
109
|
+
"Game was already running. Collected current runtime diagnostics without clearing console, launching, or stopping.";
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
session.clearConsole = await safeCollect("clear-console", () => client.executeOps([{ op: "ClearConsoleOutput" }]));
|
|
113
|
+
session.play = await safeCollect("play", () => client.play());
|
|
114
|
+
}
|
|
115
|
+
await sleep(options.playWaitMs);
|
|
116
|
+
session.diagnostics = await safeCollect("diagnostics-after-play", () => client.getDiagnostics());
|
|
117
|
+
session.console = await safeCollect("console-after-play", () => collectConsole(client, options.maxConsoleLines));
|
|
118
|
+
session.debuggerErrors = await safeCollect("debugger-errors-after-play", () => collectDebugger(client, "error", options.maxDebuggerEntries));
|
|
119
|
+
session.debuggerWarnings = await safeCollect("debugger-warnings-after-play", () => collectDebugger(client, "warning", options.maxDebuggerEntries));
|
|
120
|
+
if (!wasPlaying) {
|
|
121
|
+
session.stop = await safeCollect("stop", () => client.stop());
|
|
122
|
+
}
|
|
123
|
+
return session;
|
|
124
|
+
}
|
|
125
|
+
function redact(value) {
|
|
126
|
+
if (Array.isArray(value))
|
|
127
|
+
return value.map((item) => redact(item));
|
|
128
|
+
if (!value || typeof value !== "object")
|
|
129
|
+
return value;
|
|
130
|
+
const out = {};
|
|
131
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
132
|
+
if (/token|secret|password|authorization|cookie|api[-_]?key|hostname/i.test(key)) {
|
|
133
|
+
out[key] = "[REDACTED]";
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
out[key] = redact(nested);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
export function redactDebugReport(report) {
|
|
142
|
+
return redact(report);
|
|
143
|
+
}
|
|
144
|
+
function jsonBlock(value) {
|
|
145
|
+
return `\`\`\`json\n${JSON.stringify(redact(value), null, 2)}\n\`\`\``;
|
|
146
|
+
}
|
|
147
|
+
function doctorSummary(doctor) {
|
|
148
|
+
if (!doctor)
|
|
149
|
+
return "Doctor was skipped.";
|
|
150
|
+
return `${doctor.summary.failures} failures, ${doctor.summary.warnings} warnings, ${doctor.summary.ok} OK.`;
|
|
151
|
+
}
|
|
152
|
+
function issueText(issue) {
|
|
153
|
+
return issue && issue.trim().length > 0 ? issue.trim() : "No issue text provided.";
|
|
154
|
+
}
|
|
155
|
+
export function renderDebugReportMarkdown(report) {
|
|
156
|
+
const health = asRecord(report.engine.health);
|
|
157
|
+
const projectName = pickString(health, ["project_name", "projectName"]) ?? "unknown";
|
|
158
|
+
const projectPath = pickString(health, ["project_path", "projectPath"]) ?? "unknown";
|
|
159
|
+
const scene = pickString(health, ["scene", "scenePath", "scene_path"]) ?? "unknown";
|
|
160
|
+
return `# Summer Debug Report
|
|
161
|
+
|
|
162
|
+
Generated: ${report.generatedAt}
|
|
163
|
+
|
|
164
|
+
## Before Sending
|
|
165
|
+
|
|
166
|
+
This report is meant for Summer Engine support or an assisting AI agent. It does not include auth tokens or project file contents, but it may include local paths, project names, console output, and stack traces. Review it before sending.
|
|
167
|
+
|
|
168
|
+
## User Issue
|
|
169
|
+
|
|
170
|
+
${issueText(report.issue)}
|
|
171
|
+
|
|
172
|
+
## Quick Summary
|
|
173
|
+
|
|
174
|
+
- Doctor: ${doctorSummary(report.doctor)}
|
|
175
|
+
- Engine connected: ${report.engine.connected ? "yes" : "no"}
|
|
176
|
+
- Project: ${projectName}
|
|
177
|
+
- Project path: ${tildeify(projectPath)}
|
|
178
|
+
- Scene: ${scene}
|
|
179
|
+
|
|
180
|
+
## Environment
|
|
181
|
+
|
|
182
|
+
${jsonBlock({
|
|
183
|
+
...report.environment,
|
|
184
|
+
cwd: tildeify(report.environment.cwd),
|
|
185
|
+
hostname: report.environment.hostname ? "[local-hostname-present]" : "",
|
|
186
|
+
})}
|
|
187
|
+
|
|
188
|
+
## Doctor Checks
|
|
189
|
+
|
|
190
|
+
${report.doctor ? jsonBlock(report.doctor) : "Doctor was skipped."}
|
|
191
|
+
|
|
192
|
+
## MCP Lifecycle Log
|
|
193
|
+
|
|
194
|
+
Log path: ${tildeify(report.mcp.logPath)}
|
|
195
|
+
|
|
196
|
+
${report.mcp.recentLog.length > 0 ? `\`\`\`jsonl\n${report.mcp.recentLog.join("\n")}\n\`\`\`` : "No MCP lifecycle log entries found."}
|
|
197
|
+
|
|
198
|
+
## Engine Health
|
|
199
|
+
|
|
200
|
+
${report.engine.health ? jsonBlock(report.engine.health) : report.engine.error ?? "Engine was not reachable."}
|
|
201
|
+
|
|
202
|
+
## Project State
|
|
203
|
+
|
|
204
|
+
${report.engine.project ? jsonBlock(report.engine.project) : "No project state collected."}
|
|
205
|
+
|
|
206
|
+
## Scene State
|
|
207
|
+
|
|
208
|
+
${report.engine.scene ? jsonBlock(report.engine.scene) : "No scene state collected."}
|
|
209
|
+
|
|
210
|
+
## Diagnostics
|
|
211
|
+
|
|
212
|
+
${report.engine.diagnostics ? jsonBlock(report.engine.diagnostics) : "No diagnostics collected."}
|
|
213
|
+
|
|
214
|
+
## Console
|
|
215
|
+
|
|
216
|
+
${report.engine.console ? jsonBlock(report.engine.console) : "No console output collected."}
|
|
217
|
+
|
|
218
|
+
## Debugger Errors
|
|
219
|
+
|
|
220
|
+
${report.engine.debuggerErrors ? jsonBlock(report.engine.debuggerErrors) : "No debugger errors collected."}
|
|
221
|
+
|
|
222
|
+
## Debugger Warnings
|
|
223
|
+
|
|
224
|
+
${report.engine.debuggerWarnings ? jsonBlock(report.engine.debuggerWarnings) : "No debugger warnings collected."}
|
|
225
|
+
|
|
226
|
+
## Play Session
|
|
227
|
+
|
|
228
|
+
${report.engine.playSession ? jsonBlock(report.engine.playSession) : "No play session requested. Re-run with --play if the issue only appears while the game is running."}
|
|
229
|
+
|
|
230
|
+
## Agent Handoff Prompt
|
|
231
|
+
|
|
232
|
+
\`\`\`text
|
|
233
|
+
You are debugging a Summer Engine project. Use the Summer debug skill and this report as the starting evidence.
|
|
234
|
+
|
|
235
|
+
User issue:
|
|
236
|
+
${issueText(report.issue)}
|
|
237
|
+
|
|
238
|
+
Process:
|
|
239
|
+
1. Start with the collected diagnostics, console output, and debugger errors in this report.
|
|
240
|
+
2. Form one specific hypothesis before editing.
|
|
241
|
+
3. Ask before making project changes.
|
|
242
|
+
4. After a fix, re-run the diagnostic that found the issue and update the report or final answer with the exact evidence.
|
|
243
|
+
\`\`\`
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
export async function collectDebugReport(options = {}) {
|
|
247
|
+
const generatedAt = new Date().toISOString();
|
|
248
|
+
const includeDoctor = options.includeDoctor ?? true;
|
|
249
|
+
const report = {
|
|
250
|
+
generatedAt,
|
|
251
|
+
issue: options.issue?.trim() || null,
|
|
252
|
+
environment: {
|
|
253
|
+
cliVersion: version,
|
|
254
|
+
nodeVersion: process.version,
|
|
255
|
+
platform: platform(),
|
|
256
|
+
release: release(),
|
|
257
|
+
cwd: process.cwd(),
|
|
258
|
+
hostname: hostname(),
|
|
259
|
+
},
|
|
260
|
+
doctor: null,
|
|
261
|
+
mcp: {
|
|
262
|
+
logPath: getMcpLogPath(),
|
|
263
|
+
recentLog: [],
|
|
264
|
+
},
|
|
265
|
+
engine: {
|
|
266
|
+
connected: false,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
if (includeDoctor) {
|
|
270
|
+
report.doctor = await runDoctor({ quiet: true });
|
|
271
|
+
}
|
|
272
|
+
report.mcp.recentLog = await readRecentMcpLogLines();
|
|
273
|
+
let client;
|
|
274
|
+
try {
|
|
275
|
+
client = await EngineApiClient.connect();
|
|
276
|
+
report.engine.connected = true;
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
report.engine.error = errorMessage(error);
|
|
280
|
+
report.mcp.recentLog = await readRecentMcpLogLines();
|
|
281
|
+
return report;
|
|
282
|
+
}
|
|
283
|
+
const maxConsoleLines = options.maxConsoleLines ?? 200;
|
|
284
|
+
const maxDebuggerEntries = options.maxDebuggerEntries ?? 100;
|
|
285
|
+
report.engine.health = await safeCollect("health", () => client.health());
|
|
286
|
+
report.engine.project = await safeCollect("project-state", () => client.getProjectState());
|
|
287
|
+
report.engine.scene = await safeCollect("scene-state", () => client.getSceneState());
|
|
288
|
+
report.engine.diagnostics = await safeCollect("diagnostics", () => client.getDiagnostics());
|
|
289
|
+
report.engine.console = await safeCollect("console", () => collectConsole(client, maxConsoleLines));
|
|
290
|
+
report.engine.debuggerErrors = await safeCollect("debugger-errors", () => collectDebugger(client, "error", maxDebuggerEntries));
|
|
291
|
+
report.engine.debuggerWarnings = await safeCollect("debugger-warnings", () => collectDebugger(client, "warning", maxDebuggerEntries));
|
|
292
|
+
if (options.includePlaySession) {
|
|
293
|
+
report.engine.playSession = await collectPlaySession(client, {
|
|
294
|
+
playWaitMs: options.playWaitMs ?? 2500,
|
|
295
|
+
maxConsoleLines,
|
|
296
|
+
maxDebuggerEntries,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
report.mcp.recentLog = await readRecentMcpLogLines();
|
|
300
|
+
return report;
|
|
301
|
+
}
|
|
302
|
+
export async function createDebugReportArtifact(options = {}) {
|
|
303
|
+
const report = await collectDebugReport(options);
|
|
304
|
+
const markdown = renderDebugReportMarkdown(report);
|
|
305
|
+
const path = resolveOutputPath(report, options);
|
|
306
|
+
await writeMarkdown(path, markdown);
|
|
307
|
+
return { path, markdown, report };
|
|
308
|
+
}
|
|
@@ -249,7 +249,7 @@ function inferSkills(mode, target, goal) {
|
|
|
249
249
|
addSkill(routes, "design-mechanic", "Implement the core shooting/combat loop.", 230);
|
|
250
250
|
addSkill(routes, "design-level", "Shape the arena, spawn space, cover, and pacing.", 210);
|
|
251
251
|
}
|
|
252
|
-
addSkill(routes, "gdscript-patterns", "Use
|
|
252
|
+
addSkill(routes, "gdscript-patterns", "Use guarded Summer file tools for scripts, then verify through MCP.", 90);
|
|
253
253
|
addSkill(routes, "verification-before-completion", "Do not finish without a clean verification story.", 80);
|
|
254
254
|
return routes
|
|
255
255
|
.sort((a, b) => b.weight - a.weight)
|
|
@@ -350,7 +350,7 @@ export function buildGameTaskPlan(options) {
|
|
|
350
350
|
verification,
|
|
351
351
|
principles: [
|
|
352
352
|
"The agent should build a playable game slice, not a static demo.",
|
|
353
|
-
"Use
|
|
353
|
+
"Use identity-bound Summer file tools for code/data/docs and Summer scene tools for live hierarchy/inspector changes.",
|
|
354
354
|
"Reuse/import assets before paid generation unless the user explicitly wants custom output.",
|
|
355
355
|
"Prefer exact IDs and structured tool results over search guesses after asset creation.",
|
|
356
356
|
"Verify through the engine before claiming completion.",
|
|
@@ -358,8 +358,8 @@ export function buildGameTaskPlan(options) {
|
|
|
358
358
|
recommendedSkills: inferSkills(mode, target, goal),
|
|
359
359
|
mcpToolPlan: buildMcpToolPlan(mode, target, assetPolicy, verification, goal),
|
|
360
360
|
hostFileWork: [
|
|
361
|
-
"
|
|
362
|
-
"Use
|
|
361
|
+
"Read and mutate project text through summer_read_file, summer_replace_text, and guarded summer_write_file.",
|
|
362
|
+
"Use create_only:true for new files and an engine sha256 receipt for overwrites.",
|
|
363
363
|
"Read relevant .summer memory files when project context reports them.",
|
|
364
364
|
],
|
|
365
365
|
userGates: buildUserGates(mode, assetPolicy),
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { getSummerDir } from "./auth.js";
|
|
5
|
+
const MAX_LOG_BYTES = 512 * 1024;
|
|
6
|
+
const RETAIN_LOG_BYTES = 256 * 1024;
|
|
7
|
+
export function getMcpLogPath() {
|
|
8
|
+
return join(getSummerDir(), "mcp.log");
|
|
9
|
+
}
|
|
10
|
+
function redact(value) {
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return value.map((item) => redact(item));
|
|
13
|
+
if (!value || typeof value !== "object")
|
|
14
|
+
return value;
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
17
|
+
if (/token|secret|password|authorization|cookie|api[-_]?key/i.test(key)) {
|
|
18
|
+
out[key] = "[REDACTED]";
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
out[key] = redact(nested);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function rotateIfNeeded(path) {
|
|
27
|
+
try {
|
|
28
|
+
if (statSync(path).size < MAX_LOG_BYTES)
|
|
29
|
+
return;
|
|
30
|
+
const text = readFileSync(path, "utf-8");
|
|
31
|
+
const retained = text.slice(-RETAIN_LOG_BYTES);
|
|
32
|
+
writeFileSync(path, `{"event":"mcp:log_rotated"}\n${retained}`, {
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
mode: 0o600,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// No existing log, or not readable. The next append will recreate it.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function appendMcpLogEvent(event, details = {}) {
|
|
42
|
+
try {
|
|
43
|
+
const dir = getSummerDir();
|
|
44
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
|
+
const path = getMcpLogPath();
|
|
46
|
+
rotateIfNeeded(path);
|
|
47
|
+
const safeDetails = redact(details);
|
|
48
|
+
const payload = {
|
|
49
|
+
ts: new Date().toISOString(),
|
|
50
|
+
event,
|
|
51
|
+
...safeDetails,
|
|
52
|
+
};
|
|
53
|
+
appendFileSync(path, `${JSON.stringify(payload)}\n`, {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
mode: 0o600,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Logging must never affect MCP behavior.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function readRecentMcpLogLines(maxLines = 120) {
|
|
63
|
+
try {
|
|
64
|
+
const text = await readFile(getMcpLogPath(), "utf-8");
|
|
65
|
+
if (!text.trim())
|
|
66
|
+
return [];
|
|
67
|
+
return text.trimEnd().split(/\r?\n/).slice(-maxLines);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -23,7 +23,7 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
23
23
|
readonly public: true;
|
|
24
24
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "kilo-code", "gemini", "github-copilot", "vscode-copilot", "opencode"];
|
|
25
25
|
readonly recommended: true;
|
|
26
|
-
readonly requiresMcpTools: readonly ["summer_get_console", "summer_get_debugger_errors", "summer_get_script_errors", "summer_get_diagnostics", "summer_clear_console", "summer_play", "summer_stop", "summer_inspect_node"];
|
|
26
|
+
readonly requiresMcpTools: readonly ["summer_create_debug_report", "summer_get_console", "summer_get_debugger_errors", "summer_get_script_errors", "summer_get_diagnostics", "summer_clear_console", "summer_play", "summer_stop", "summer_inspect_node"];
|
|
27
27
|
readonly testScenario: "Triage a runtime crash end-to-end: read errors, locate code, propose fix, apply on approval, verify.";
|
|
28
28
|
}, {
|
|
29
29
|
readonly name: "play";
|
|
@@ -49,6 +49,14 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
49
49
|
readonly recommended: true;
|
|
50
50
|
readonly requiresMcpTools: readonly ["summer_create_scene", "summer_open_scene", "summer_get_scene_tree", "summer_add_node", "summer_instantiate_scene", "summer_save_scene"];
|
|
51
51
|
readonly testScenario: "Build a small reusable player scene, instantiate it in a main scene, and save both scenes through Summer tools.";
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "summer-cloud";
|
|
54
|
+
readonly category: "scene-and-project";
|
|
55
|
+
readonly public: true;
|
|
56
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "kilo-code", "gemini", "github-copilot", "vscode-copilot", "opencode"];
|
|
57
|
+
readonly recommended: true;
|
|
58
|
+
readonly requiresMcpTools: readonly ["summer_cloud_init", "summer_cloud_status", "summer_cloud_push", "summer_cloud_pull", "summer_cloud_restore", "summer_cloud_checkpoints", "summer_cloud_conflicts"];
|
|
59
|
+
readonly testScenario: "Enable Cloud for a project, push it, then pull it onto a second machine, taking the bootstrap choice and surfacing the delete count before any guarded deletion.";
|
|
52
60
|
}, {
|
|
53
61
|
readonly name: "fps-controller";
|
|
54
62
|
readonly category: "character-controllers";
|
|
@@ -201,6 +209,14 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
201
209
|
readonly recommended: true;
|
|
202
210
|
readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_project_setting", "summer_get_console", "summer_get_diagnostics"];
|
|
203
211
|
readonly testScenario: "Run the Steam pre-flight checklist (Windows + Mac + Linux), validate icons/banners/screenshots/build config, and produce a release build only when green.";
|
|
212
|
+
}, {
|
|
213
|
+
readonly name: "remote-deploy";
|
|
214
|
+
readonly category: "deployment";
|
|
215
|
+
readonly public: true;
|
|
216
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "kilo-code", "gemini", "github-copilot", "vscode-copilot", "opencode"];
|
|
217
|
+
readonly recommended: true;
|
|
218
|
+
readonly requiresMcpTools: readonly ["summer_project_setting", "summer_get_diagnostics", "summer_get_console", "summer_get_script_errors"];
|
|
219
|
+
readonly testScenario: "User says \"deploy the game to my Android phone\" — skill walks the prerequisites (runnable export preset, installed export templates, USB-debugging device), checks diagnostics are clean, and points them at the topnav Remote Deploy button to deploy a debug build with on-device remote debugging.";
|
|
204
220
|
}, {
|
|
205
221
|
readonly name: "concept-art";
|
|
206
222
|
readonly category: "2d-assets";
|
|
@@ -249,6 +265,14 @@ export declare const SKILL_REGISTRY: readonly [{
|
|
|
249
265
|
readonly recommended: false;
|
|
250
266
|
readonly requiresMcpTools: readonly ["summer_generate_image", "summer_import_from_url", "summer_set_resource_property", "summer_add_node", "summer_set_prop"];
|
|
251
267
|
readonly testScenario: "User requests a '4-frame walk cycle for the goblin' — skill generates per-frame at locked anchor, builds SpriteFrames resource, wires AnimatedSprite2D.";
|
|
268
|
+
}, {
|
|
269
|
+
readonly name: "create-asset-sheet";
|
|
270
|
+
readonly category: "2d-assets";
|
|
271
|
+
readonly public: true;
|
|
272
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf", "cline", "roo-code", "kilo-code", "gemini", "github-copilot", "vscode-copilot", "opencode"];
|
|
273
|
+
readonly recommended: false;
|
|
274
|
+
readonly requiresMcpTools: readonly ["summer_get_studio_workflow", "summer_generate_image", "summer_slice_asset_sheet", "summer_import_asset_by_id"];
|
|
275
|
+
readonly testScenario: "User requests a Japanese village asset pack — skill loads the Guided asset-pack recipe, generates one coherent sheet, slices it through the shared cloud pipeline, and imports selected results.";
|
|
252
276
|
}, {
|
|
253
277
|
readonly name: "skybox-panorama";
|
|
254
278
|
readonly category: "2d-assets";
|
|
@@ -53,6 +53,7 @@ export const SKILL_REGISTRY = [
|
|
|
53
53
|
clients: ALL_CLIENTS,
|
|
54
54
|
recommended: true,
|
|
55
55
|
requiresMcpTools: [
|
|
56
|
+
"summer_create_debug_report",
|
|
56
57
|
"summer_get_console",
|
|
57
58
|
"summer_get_debugger_errors",
|
|
58
59
|
"summer_get_script_errors",
|
|
@@ -108,6 +109,23 @@ export const SKILL_REGISTRY = [
|
|
|
108
109
|
],
|
|
109
110
|
testScenario: "Build a small reusable player scene, instantiate it in a main scene, and save both scenes through Summer tools.",
|
|
110
111
|
},
|
|
112
|
+
{
|
|
113
|
+
name: "summer-cloud",
|
|
114
|
+
category: "scene-and-project",
|
|
115
|
+
public: true,
|
|
116
|
+
clients: ALL_CLIENTS,
|
|
117
|
+
recommended: true,
|
|
118
|
+
requiresMcpTools: [
|
|
119
|
+
"summer_cloud_init",
|
|
120
|
+
"summer_cloud_status",
|
|
121
|
+
"summer_cloud_push",
|
|
122
|
+
"summer_cloud_pull",
|
|
123
|
+
"summer_cloud_restore",
|
|
124
|
+
"summer_cloud_checkpoints",
|
|
125
|
+
"summer_cloud_conflicts",
|
|
126
|
+
],
|
|
127
|
+
testScenario: "Enable Cloud for a project, push it, then pull it onto a second machine, taking the bootstrap choice and surfacing the delete count before any guarded deletion.",
|
|
128
|
+
},
|
|
111
129
|
{
|
|
112
130
|
name: "fps-controller",
|
|
113
131
|
category: "character-controllers",
|
|
@@ -413,6 +431,20 @@ export const SKILL_REGISTRY = [
|
|
|
413
431
|
],
|
|
414
432
|
testScenario: "Run the Steam pre-flight checklist (Windows + Mac + Linux), validate icons/banners/screenshots/build config, and produce a release build only when green.",
|
|
415
433
|
},
|
|
434
|
+
{
|
|
435
|
+
name: "remote-deploy",
|
|
436
|
+
category: "deployment",
|
|
437
|
+
public: true,
|
|
438
|
+
clients: ALL_CLIENTS,
|
|
439
|
+
recommended: true,
|
|
440
|
+
requiresMcpTools: [
|
|
441
|
+
"summer_project_setting",
|
|
442
|
+
"summer_get_diagnostics",
|
|
443
|
+
"summer_get_console",
|
|
444
|
+
"summer_get_script_errors",
|
|
445
|
+
],
|
|
446
|
+
testScenario: "User says \"deploy the game to my Android phone\" — skill walks the prerequisites (runnable export preset, installed export templates, USB-debugging device), checks diagnostics are clean, and points them at the topnav Remote Deploy button to deploy a debug build with on-device remote debugging.",
|
|
447
|
+
},
|
|
416
448
|
{
|
|
417
449
|
name: "concept-art",
|
|
418
450
|
category: "2d-assets",
|
|
@@ -494,6 +526,20 @@ export const SKILL_REGISTRY = [
|
|
|
494
526
|
],
|
|
495
527
|
testScenario: "User requests a '4-frame walk cycle for the goblin' — skill generates per-frame at locked anchor, builds SpriteFrames resource, wires AnimatedSprite2D.",
|
|
496
528
|
},
|
|
529
|
+
{
|
|
530
|
+
name: "create-asset-sheet",
|
|
531
|
+
category: "2d-assets",
|
|
532
|
+
public: true,
|
|
533
|
+
clients: ALL_CLIENTS,
|
|
534
|
+
recommended: false,
|
|
535
|
+
requiresMcpTools: [
|
|
536
|
+
"summer_get_studio_workflow",
|
|
537
|
+
"summer_generate_image",
|
|
538
|
+
"summer_slice_asset_sheet",
|
|
539
|
+
"summer_import_asset_by_id",
|
|
540
|
+
],
|
|
541
|
+
testScenario: "User requests a Japanese village asset pack — skill loads the Guided asset-pack recipe, generates one coherent sheet, slices it through the shared cloud pipeline, and imports selected results.",
|
|
542
|
+
},
|
|
497
543
|
{
|
|
498
544
|
name: "skybox-panorama",
|
|
499
545
|
category: "2d-assets",
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EngineApiClient } from "../lib/api-client.js";
|
|
2
|
-
import {
|
|
3
|
-
export declare
|
|
2
|
+
import { getCachedBootDriftNotice as getCachedNotice } from "../lib/mcp-boot-notice.js";
|
|
3
|
+
export declare const getCachedBootDriftNotice: typeof getCachedNotice;
|
|
4
4
|
export declare function getClient(): Promise<EngineApiClient>;
|
|
5
5
|
export declare function resetClient(): void;
|
|
6
6
|
export declare function startMcpServer(): Promise<void>;
|