summer-engine 2.6.6 → 2.8.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 +4 -1
- package/.codex-plugin/plugin.json +6 -2
- package/.cursor-plugin/plugin.json +5 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +2 -2
- package/AGENTS.md +11 -7
- package/CHANGELOG.md +40 -0
- package/GEMINI.md +9 -7
- package/README.md +63 -21
- package/_persona/summer/SKILL.md +1 -0
- package/assets/autopilot/README.md +51 -0
- package/assets/autopilot/autopilot.gd +222 -0
- package/assets/autopilot/probe_base.gd +262 -0
- package/assets/autopilot/run.sh +78 -0
- package/commands/gameskill.md +11 -2
- package/commands/summer.md +13 -0
- package/dist/bin/summer.js +12 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +38 -6
- 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/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +123 -36
- package/dist/commands/logout.js +3 -16
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +19 -0
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +20 -0
- package/dist/commands/releases.d.ts +2 -0
- package/dist/commands/releases.js +20 -0
- package/dist/commands/run.js +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/lib/api-client.d.ts +15 -4
- package/dist/lib/api-client.js +138 -13
- package/dist/lib/async-op-lifecycle.d.ts +7 -2
- package/dist/lib/async-op-lifecycle.js +32 -9
- package/dist/lib/auth.d.ts +40 -12
- package/dist/lib/auth.js +173 -37
- package/dist/lib/cloud/engine-bridge.d.ts +17 -4
- package/dist/lib/cloud/engine-bridge.js +32 -6
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +142 -0
- package/dist/lib/creator.d.ts +93 -0
- package/dist/lib/creator.js +393 -0
- package/dist/lib/debug-report.d.ts +50 -0
- package/dist/lib/debug-report.js +308 -0
- package/dist/lib/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- 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/public-surface-paths.d.ts +13 -0
- package/dist/lib/public-surface-paths.js +42 -0
- package/dist/lib/skills-registry.d.ts +33 -1
- package/dist/lib/skills-registry.js +55 -0
- package/dist/lib/store.d.ts +14 -0
- package/dist/lib/store.js +145 -0
- package/dist/lib/summer-compatibility.d.ts +6 -0
- package/dist/lib/summer-compatibility.js +22 -0
- package/dist/mcp/server.d.ts +10 -3
- package/dist/mcp/server.js +104 -11
- package/dist/mcp/tools/asset-tools.js +33 -12
- package/dist/mcp/tools/cloud-tools.js +15 -15
- package/dist/mcp/tools/creator-tools.d.ts +2 -0
- package/dist/mcp/tools/creator-tools.js +105 -0
- 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 +159 -11
- package/dist/mcp/tools/project-tools.js +37 -27
- package/dist/mcp/tools/scene-tools.js +106 -29
- package/dist/mcp/tools/visual-tools.js +4 -3
- package/dist/mcp/tools/with-engine.js +4 -4
- package/hooks/session-start.sh +5 -5
- package/package.json +3 -1
- package/references/collaborative-protocol.md +50 -0
- package/references/gd-style.md +121 -0
- package/references/godot-version.md +51 -0
- package/references/mcp-tools-reference.md +199 -0
- package/references/public-surface-language.md +30 -0
- package/references/summer-folder.md +90 -0
- package/references/template-registry.md +73 -0
- package/skills/2d-assets/character-portrait/SKILL.md +17 -12
- package/skills/2d-assets/concept-art/SKILL.md +10 -9
- package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
- package/skills/2d-assets/pixel-art/SKILL.md +36 -20
- package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
- package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
- package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
- package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
- package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
- package/skills/3d-assets/character-model/SKILL.md +34 -19
- package/skills/3d-assets/environment-kit/SKILL.md +11 -5
- package/skills/3d-assets/organic-model/SKILL.md +8 -5
- package/skills/3d-assets/prop-model/SKILL.md +9 -3
- package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
- package/skills/README.md +1 -1
- package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
- package/skills/animation/animation-tree/SKILL.md +39 -15
- package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
- package/skills/animation/generate-motion/SKILL.md +47 -15
- package/skills/animation/procedural-animation/SKILL.md +31 -13
- package/skills/animation/retarget/SKILL.md +127 -79
- package/skills/audio/adaptive-music/SKILL.md +24 -20
- package/skills/audio/ambient-bed/SKILL.md +22 -16
- package/skills/audio/audio-direction/SKILL.md +15 -10
- package/skills/audio/music-track/SKILL.md +18 -12
- package/skills/audio/sound-effect/SKILL.md +17 -12
- package/skills/audio/voice-line/SKILL.md +42 -42
- package/skills/character-controllers/fps-controller/SKILL.md +26 -16
- package/skills/debugging/debug/SKILL.md +74 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +140 -0
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
- package/skills/level-design/design-level/SKILL.md +22 -8
- package/skills/level-design/scene-to-level/SKILL.md +13 -7
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
- package/skills/performance/tune-performance/SKILL.md +51 -32
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
- package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
- package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
- package/skills/scene-and-project/make-game/SKILL.md +2 -1
- package/skills/scene-and-project/play/SKILL.md +10 -4
- package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
- package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
- package/skills/video/animated-loop/SKILL.md +63 -39
- package/skills/video/cinematic-cutscene/SKILL.md +66 -30
- package/skills/video/trailer-shot/SKILL.md +30 -21
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
- package/skills/visual-effects/game-feel/SKILL.md +51 -33
- package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
- package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
- package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
- package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
- package/skills/workflow/brainstorming/SKILL.md +1 -1
- package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
- package/skills/workflow/gameskill/SKILL.md +11 -2
- package/skills/workflow/headless-scripting/SKILL.md +407 -0
- package/skills/workflow/investigating-bugs/SKILL.md +4 -2
- package/skills/workflow/mcpupdate/SKILL.md +9 -9
- package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
- package/skills/workflow/skill-create/SKILL.md +7 -7
- package/skills/workflow/skill-test/SKILL.md +3 -3
- package/skills/workflow/using-summer/SKILL.md +32 -3
- package/skills/workflow/verification-before-completion/SKILL.md +2 -0
- package/skills/workflow/writing-plans/SKILL.md +7 -7
- package/skills/workflow/writing-skills/SKILL.md +7 -5
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, setConfigValue, unsetConfigValue, } from "../lib/config.js";
|
|
3
|
+
function requireKey(value) {
|
|
4
|
+
if (isConfigKey(value))
|
|
5
|
+
return value;
|
|
6
|
+
throw new Error(`Unknown config key "${value}". Recovery: use one of ${CONFIG_KEYS.join(", ")}.`);
|
|
7
|
+
}
|
|
8
|
+
async function printConfig(json) {
|
|
9
|
+
const config = await readSummerConfig();
|
|
10
|
+
const values = Object.fromEntries(CONFIG_KEYS.map((key) => [key, getConfigValue(config, key) ?? null]));
|
|
11
|
+
if (json) {
|
|
12
|
+
console.log(JSON.stringify(values, null, 2));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const [key, value] of Object.entries(values)) {
|
|
16
|
+
console.log(`${key}=${value ?? "(not set)"}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const configCommand = new Command("config")
|
|
20
|
+
.description("Read or update the shared ~/.summer configuration")
|
|
21
|
+
.option("--json", "Print all configuration as JSON")
|
|
22
|
+
.action(async (opts) => printConfig(Boolean(opts.json)));
|
|
23
|
+
configCommand
|
|
24
|
+
.command("get")
|
|
25
|
+
.description("Read one configuration value")
|
|
26
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
27
|
+
.action(async (rawKey) => {
|
|
28
|
+
const key = requireKey(rawKey);
|
|
29
|
+
const value = getConfigValue(await readSummerConfig(), key);
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
throw new Error(`${key} is not set. Recovery: run "summer config set ${key} <value>", or rely on the documented default.`);
|
|
32
|
+
}
|
|
33
|
+
console.log(value);
|
|
34
|
+
});
|
|
35
|
+
configCommand
|
|
36
|
+
.command("set")
|
|
37
|
+
.description("Set one non-secret configuration value")
|
|
38
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
39
|
+
.argument("<value>")
|
|
40
|
+
.action(async (rawKey, value) => {
|
|
41
|
+
const key = requireKey(rawKey);
|
|
42
|
+
await setConfigValue(key, value);
|
|
43
|
+
console.log(`Set ${key}.`);
|
|
44
|
+
});
|
|
45
|
+
configCommand
|
|
46
|
+
.command("unset")
|
|
47
|
+
.description("Remove one configuration override")
|
|
48
|
+
.argument("<key>", `One of: ${CONFIG_KEYS.join(", ")}`)
|
|
49
|
+
.action(async (rawKey) => {
|
|
50
|
+
const key = requireKey(rawKey);
|
|
51
|
+
await unsetConfigValue(key);
|
|
52
|
+
console.log(`Unset ${key}.`);
|
|
53
|
+
});
|
package/dist/commands/create.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
-
import { join, resolve } from "path";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join, resolve } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
4
5
|
import { fetchRemoteTemplates, cloneTemplate, matchTemplate } from "../lib/remote-templates.js";
|
|
6
|
+
import { SUMMER_ENGINE_COMPATIBILITY } from "../lib/summer-compatibility.js";
|
|
5
7
|
const BUILTIN_TEMPLATES = [
|
|
6
8
|
{
|
|
7
9
|
name: "empty",
|
|
8
10
|
description: "Empty 3D project with just a root node",
|
|
9
11
|
builtin: true,
|
|
10
12
|
generate: (dir, name) => {
|
|
11
|
-
writeFileSync(join(dir, "project.godot"),
|
|
13
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
12
14
|
writeFileSync(join(dir, "main.tscn"), emptyScene());
|
|
13
15
|
},
|
|
14
16
|
},
|
|
@@ -17,11 +19,34 @@ const BUILTIN_TEMPLATES = [
|
|
|
17
19
|
description: "3D scene with camera, light, and floor",
|
|
18
20
|
builtin: true,
|
|
19
21
|
generate: (dir, name) => {
|
|
20
|
-
writeFileSync(join(dir, "project.godot"),
|
|
22
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
21
23
|
writeFileSync(join(dir, "main.tscn"), basicScene3D());
|
|
22
24
|
},
|
|
23
25
|
},
|
|
24
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Copy the autopilot verification scaffold into a fresh project.
|
|
29
|
+
*
|
|
30
|
+
* Every project gets a working example of how to prove its own gameplay: a probe
|
|
31
|
+
* that drives the player with real input, saves rendered frames, asserts, and
|
|
32
|
+
* exits. Without it every agent reinvents verification from scratch, and most
|
|
33
|
+
* settle for asking the user to play the game instead.
|
|
34
|
+
*
|
|
35
|
+
* Never overwrites — an existing tests/autopilot/ is the user's, not ours.
|
|
36
|
+
*/
|
|
37
|
+
function scaffoldAutopilot(projectDir) {
|
|
38
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
// dist/commands/create.js -> package root
|
|
40
|
+
const source = resolve(here, "..", "..", "assets", "autopilot");
|
|
41
|
+
const target = join(projectDir, "tests", "autopilot");
|
|
42
|
+
if (!existsSync(source) || existsSync(target))
|
|
43
|
+
return false;
|
|
44
|
+
mkdirSync(target, { recursive: true });
|
|
45
|
+
for (const entry of readdirSync(source)) {
|
|
46
|
+
copyFileSync(join(source, entry), join(target, entry));
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
25
50
|
export const createCommand = new Command("create")
|
|
26
51
|
.description("Create a new Summer Engine project. Built-in templates work offline; community templates clone from github.com/SummerEngine/template-*.")
|
|
27
52
|
.argument("<template>", `Template slug. Built-in: ${BUILTIN_TEMPLATES.map((t) => t.name).join(", ")}. Or any community slug — see "summer list templates".`)
|
|
@@ -39,9 +64,12 @@ export const createCommand = new Command("create")
|
|
|
39
64
|
console.log(`Creating project from built-in '${templateName}' template...`);
|
|
40
65
|
mkdirSync(fullPath, { recursive: true });
|
|
41
66
|
builtin.generate(fullPath, dirName);
|
|
67
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
42
68
|
console.log(`\nProject created at ${fullPath}`);
|
|
43
69
|
console.log("\nNext steps:");
|
|
44
70
|
console.log(` summer run ${dirName}`);
|
|
71
|
+
if (scaffolded)
|
|
72
|
+
console.log(` bash ${dirName}/tests/autopilot/run.sh (verify the game without opening it)`);
|
|
45
73
|
console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
|
|
46
74
|
return;
|
|
47
75
|
}
|
|
@@ -83,21 +111,25 @@ export const createCommand = new Command("create")
|
|
|
83
111
|
console.error(err.message);
|
|
84
112
|
process.exit(1);
|
|
85
113
|
}
|
|
114
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
86
115
|
console.log(`\nProject created at ${fullPath}`);
|
|
87
116
|
console.log("Source: " + remote.url);
|
|
88
117
|
console.log("\nNext steps:");
|
|
89
118
|
console.log(` summer run ${dirName}`);
|
|
119
|
+
if (scaffolded)
|
|
120
|
+
console.log(` bash ${dirName}/tests/autopilot/run.sh (verify the game without opening it)`);
|
|
90
121
|
console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
|
|
91
122
|
});
|
|
92
|
-
function
|
|
123
|
+
export function renderProjectSettings(name, mainScene) {
|
|
93
124
|
return `; Summer Engine Project
|
|
94
125
|
; Generated by summer-engine CLI
|
|
126
|
+
; Technical base ${SUMMER_ENGINE_COMPATIBILITY.currentTechnicalBaseVersion}; Summer follows upstream continuously
|
|
95
127
|
|
|
96
128
|
[application]
|
|
97
129
|
|
|
98
130
|
config/name="${name}"
|
|
99
131
|
run/main_scene="${mainScene}"
|
|
100
|
-
config/features=PackedStringArray("
|
|
132
|
+
config/features=PackedStringArray("${SUMMER_ENGINE_COMPATIBILITY.projectFeatureTag}")
|
|
101
133
|
|
|
102
134
|
[rendering]
|
|
103
135
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createDebugReportArtifact, redactDebugReport } from "../lib/debug-report.js";
|
|
3
|
+
import { c, sym, tildeify } from "../lib/format.js";
|
|
4
|
+
function parsePositiveInt(value, fallback) {
|
|
5
|
+
if (!value)
|
|
6
|
+
return fallback;
|
|
7
|
+
const parsed = Number.parseInt(value, 10);
|
|
8
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
9
|
+
}
|
|
10
|
+
export const debugCommand = new Command("debug")
|
|
11
|
+
.description("Create a support-ready Markdown debug report")
|
|
12
|
+
.argument("[issue...]", "What is broken or surprising?")
|
|
13
|
+
.option("-o, --output <path>", "Write the report to this path")
|
|
14
|
+
.option("--json", "Print JSON with the report path and collected data")
|
|
15
|
+
.option("--play", "Also launch the game briefly and collect post-play diagnostics")
|
|
16
|
+
.option("--no-doctor", "Skip summer doctor checks")
|
|
17
|
+
.option("--max-console-lines <n>", "Console lines to collect", "200")
|
|
18
|
+
.option("--max-debugger-entries <n>", "Debugger entries to collect", "100")
|
|
19
|
+
.option("--play-wait-ms <n>", "Milliseconds to wait after --play starts", "2500")
|
|
20
|
+
.action(async (issueParts, opts) => {
|
|
21
|
+
const issue = issueParts.join(" ").trim();
|
|
22
|
+
const artifact = await createDebugReportArtifact({
|
|
23
|
+
issue,
|
|
24
|
+
outputPath: opts.output,
|
|
25
|
+
includePlaySession: Boolean(opts.play),
|
|
26
|
+
includeDoctor: opts.doctor !== false,
|
|
27
|
+
maxConsoleLines: parsePositiveInt(opts.maxConsoleLines, 200),
|
|
28
|
+
maxDebuggerEntries: parsePositiveInt(opts.maxDebuggerEntries, 100),
|
|
29
|
+
playWaitMs: parsePositiveInt(opts.playWaitMs, 2500),
|
|
30
|
+
});
|
|
31
|
+
if (opts.json) {
|
|
32
|
+
console.log(JSON.stringify({
|
|
33
|
+
ok: true,
|
|
34
|
+
path: artifact.path,
|
|
35
|
+
report: redactDebugReport(artifact.report),
|
|
36
|
+
}, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(`${sym.ok()} ${c.bold("Summer debug report created")}`);
|
|
41
|
+
console.log(` ${tildeify(artifact.path)}`);
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log("Review it before sending. It omits auth tokens, but may include local paths and stack traces.");
|
|
44
|
+
});
|
package/dist/commands/doctor.js
CHANGED
|
@@ -3,14 +3,23 @@ import { existsSync } from "fs";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { platform } from "os";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
8
|
import { getAuthToken, getUserInfo } from "../lib/auth.js";
|
|
7
9
|
import { isGitAvailable } from "../lib/cloud/checkpoint.js";
|
|
8
10
|
import { checkEngineHealth, getApiPort, getApiToken } from "../lib/engine.js";
|
|
9
11
|
import { brandLine, c, pad, sym, tildeify } from "../lib/format.js";
|
|
12
|
+
import { getMcpLogPath } from "../lib/mcp-log.js";
|
|
10
13
|
import { getProjectMemorySummary } from "../lib/project-memory.js";
|
|
11
14
|
import { buildCliVersionCheck, buildSkillsVersionCheck, defaultSkillMarkerCandidates, fetchLatestRegistryVersion, } from "../lib/version-check.js";
|
|
12
15
|
const require = createRequire(import.meta.url);
|
|
13
16
|
const { version } = require("../../package.json");
|
|
17
|
+
const MIN_EXPECTED_MCP_TOOLS = 50;
|
|
18
|
+
const REQUIRED_MCP_TOOLS = [
|
|
19
|
+
"summer_get_project_context",
|
|
20
|
+
"summer_generate_image",
|
|
21
|
+
"summer_create_debug_report",
|
|
22
|
+
];
|
|
14
23
|
const MAC_ENGINE_PATHS = [
|
|
15
24
|
"/Applications/Summer.app/Contents/MacOS/Summer",
|
|
16
25
|
`${process.env.HOME}/Applications/Summer.app/Contents/MacOS/Summer`,
|
|
@@ -21,7 +30,7 @@ const WIN_ENGINE_PATHS = [
|
|
|
21
30
|
`${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
|
|
22
31
|
];
|
|
23
32
|
export const doctorCommand = new Command("doctor")
|
|
24
|
-
.description("Diagnose Node, login, engine, project memory, local API, and MCP
|
|
33
|
+
.description("Diagnose Node, login, engine, project memory, local API, and MCP registration")
|
|
25
34
|
.option("--json", "Print diagnostics as JSON")
|
|
26
35
|
.action(async (opts) => {
|
|
27
36
|
if (!opts.json) {
|
|
@@ -53,6 +62,7 @@ export async function runDoctor(options = {}) {
|
|
|
53
62
|
checks.push(await checkLocalApi());
|
|
54
63
|
checks.push(await checkProjectMemory());
|
|
55
64
|
checks.push(await checkMcpBoot());
|
|
65
|
+
checks.push(await checkMcpToolsList());
|
|
56
66
|
const result = summarizeChecks(checks);
|
|
57
67
|
if (!options.quiet) {
|
|
58
68
|
if (options.json) {
|
|
@@ -237,8 +247,8 @@ async function checkProjectMemory() {
|
|
|
237
247
|
};
|
|
238
248
|
}
|
|
239
249
|
async function checkMcpBoot() {
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
250
|
+
const resolved = resolveCurrentMcpCommand();
|
|
251
|
+
if (!resolved) {
|
|
242
252
|
return {
|
|
243
253
|
id: "mcp-boot",
|
|
244
254
|
label: "MCP Server",
|
|
@@ -247,9 +257,7 @@ async function checkMcpBoot() {
|
|
|
247
257
|
};
|
|
248
258
|
}
|
|
249
259
|
return new Promise((resolve) => {
|
|
250
|
-
const
|
|
251
|
-
const args = cliPath.endsWith(".js") ? [cliPath, "mcp"] : ["mcp"];
|
|
252
|
-
const child = spawn(command, args, {
|
|
260
|
+
const child = spawn(resolved.command, resolved.args, {
|
|
253
261
|
env: process.env,
|
|
254
262
|
stdio: ["pipe", "pipe", "pipe"],
|
|
255
263
|
});
|
|
@@ -269,7 +277,7 @@ async function checkMcpBoot() {
|
|
|
269
277
|
label: "MCP Server",
|
|
270
278
|
status: "fail",
|
|
271
279
|
message: "MCP server did not finish startup within 3 seconds.",
|
|
272
|
-
details: trimOutput({ stdout, stderr }),
|
|
280
|
+
details: { ...trimOutput({ stdout, stderr }), ...resolved },
|
|
273
281
|
});
|
|
274
282
|
}, 3000);
|
|
275
283
|
child.stderr.on("data", (chunk) => {
|
|
@@ -281,6 +289,7 @@ async function checkMcpBoot() {
|
|
|
281
289
|
label: "MCP Server",
|
|
282
290
|
status: "ok",
|
|
283
291
|
message: "ready",
|
|
292
|
+
details: { ...resolved },
|
|
284
293
|
});
|
|
285
294
|
}
|
|
286
295
|
});
|
|
@@ -294,6 +303,7 @@ async function checkMcpBoot() {
|
|
|
294
303
|
label: "MCP Server",
|
|
295
304
|
status: "fail",
|
|
296
305
|
message: `Could not start MCP server: ${error.message}`,
|
|
306
|
+
details: { ...resolved },
|
|
297
307
|
});
|
|
298
308
|
});
|
|
299
309
|
child.on("exit", (code) => {
|
|
@@ -304,12 +314,106 @@ async function checkMcpBoot() {
|
|
|
304
314
|
label: "MCP Server",
|
|
305
315
|
status: "fail",
|
|
306
316
|
message: `MCP server exited before reporting readiness${code === null ? "" : ` (code ${code})`}.`,
|
|
307
|
-
details: trimOutput({ stdout, stderr }),
|
|
317
|
+
details: { ...trimOutput({ stdout, stderr }), ...resolved },
|
|
308
318
|
});
|
|
309
319
|
}
|
|
310
320
|
});
|
|
311
321
|
});
|
|
312
322
|
}
|
|
323
|
+
function resolveCurrentMcpCommand() {
|
|
324
|
+
const cliPath = process.argv[1];
|
|
325
|
+
if (!cliPath)
|
|
326
|
+
return null;
|
|
327
|
+
return cliPath.endsWith(".js")
|
|
328
|
+
? { command: process.execPath, args: [cliPath, "mcp"] }
|
|
329
|
+
: { command: cliPath, args: ["mcp"] };
|
|
330
|
+
}
|
|
331
|
+
function inheritedEnv(extra = {}) {
|
|
332
|
+
const env = {};
|
|
333
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
334
|
+
if (value !== undefined)
|
|
335
|
+
env[key] = value;
|
|
336
|
+
}
|
|
337
|
+
return { ...env, ...extra };
|
|
338
|
+
}
|
|
339
|
+
async function checkMcpToolsList() {
|
|
340
|
+
const resolved = resolveCurrentMcpCommand();
|
|
341
|
+
if (!resolved) {
|
|
342
|
+
return {
|
|
343
|
+
id: "mcp-tools-list",
|
|
344
|
+
label: "MCP Tools",
|
|
345
|
+
status: "fail",
|
|
346
|
+
message: "Could not resolve the current Summer CLI entrypoint.",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const client = new Client({ name: "summer-doctor", version });
|
|
350
|
+
const transport = new StdioClientTransport({
|
|
351
|
+
...resolved,
|
|
352
|
+
env: inheritedEnv({ SUMMER_MCP_DOCTOR: "1" }),
|
|
353
|
+
stderr: "pipe",
|
|
354
|
+
});
|
|
355
|
+
let stderr = "";
|
|
356
|
+
transport.stderr?.on("data", (chunk) => {
|
|
357
|
+
stderr += Buffer.isBuffer(chunk) ? chunk.toString("utf-8") : String(chunk);
|
|
358
|
+
});
|
|
359
|
+
try {
|
|
360
|
+
await client.connect(transport, { timeout: 5000 });
|
|
361
|
+
const listed = await client.listTools(undefined, { timeout: 5000 });
|
|
362
|
+
const toolNames = listed.tools.map((tool) => tool.name).sort();
|
|
363
|
+
const missing = REQUIRED_MCP_TOOLS.filter((tool) => !toolNames.includes(tool));
|
|
364
|
+
const details = {
|
|
365
|
+
...resolved,
|
|
366
|
+
toolCount: toolNames.length,
|
|
367
|
+
requiredTools: REQUIRED_MCP_TOOLS,
|
|
368
|
+
missing,
|
|
369
|
+
server: client.getServerVersion(),
|
|
370
|
+
stderr: stderr.trim().slice(-1000),
|
|
371
|
+
logPath: getMcpLogPath(),
|
|
372
|
+
};
|
|
373
|
+
if (missing.length > 0) {
|
|
374
|
+
return {
|
|
375
|
+
id: "mcp-tools-list",
|
|
376
|
+
label: "MCP Tools",
|
|
377
|
+
status: "fail",
|
|
378
|
+
message: `missing required tools: ${missing.join(", ")}`,
|
|
379
|
+
details,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (toolNames.length < MIN_EXPECTED_MCP_TOOLS) {
|
|
383
|
+
return {
|
|
384
|
+
id: "mcp-tools-list",
|
|
385
|
+
label: "MCP Tools",
|
|
386
|
+
status: "fail",
|
|
387
|
+
message: `only ${toolNames.length} tools listed; expected ${MIN_EXPECTED_MCP_TOOLS}+`,
|
|
388
|
+
details,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
id: "mcp-tools-list",
|
|
393
|
+
label: "MCP Tools",
|
|
394
|
+
status: "ok",
|
|
395
|
+
message: `${toolNames.length} tools registered`,
|
|
396
|
+
details,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
return {
|
|
401
|
+
id: "mcp-tools-list",
|
|
402
|
+
label: "MCP Tools",
|
|
403
|
+
status: "fail",
|
|
404
|
+
message: `protocol handshake/list failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
405
|
+
details: {
|
|
406
|
+
...resolved,
|
|
407
|
+
stderr: stderr.trim().slice(-1000),
|
|
408
|
+
logPath: getMcpLogPath(),
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
await client.close().catch(() => undefined);
|
|
414
|
+
await transport.close().catch(() => undefined);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
313
417
|
function summarizeChecks(checks) {
|
|
314
418
|
const summary = {
|
|
315
419
|
ok: checks.filter((check) => check.status === "ok").length,
|
package/dist/commands/list.js
CHANGED
|
@@ -55,7 +55,7 @@ function listProjects() {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
if (projects.length === 0) {
|
|
58
|
-
console.log("No Summer Engine
|
|
58
|
+
console.log("No Summer Engine projects found in current directory.");
|
|
59
59
|
console.log("\nCreate one: summer create 3d-basic my-game");
|
|
60
60
|
return;
|
|
61
61
|
}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare const loginCommand: Command;
|
|
3
|
+
export interface CreatorLoginDependencies {
|
|
4
|
+
openUrl: (url: string) => Promise<unknown>;
|
|
5
|
+
readSecret: (prompt: string) => Promise<string>;
|
|
6
|
+
log: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The browser session mints the token; the token is pasted once into a hidden
|
|
10
|
+
* prompt. This deliberately does not replace the core Summer login JWT.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runCreatorLogin(overrides?: Partial<CreatorLoginDependencies>): Promise<void>;
|
|
13
|
+
export interface LoginDependencies {
|
|
14
|
+
fetch: typeof fetch;
|
|
15
|
+
openUrl: (url: string) => Promise<unknown>;
|
|
16
|
+
randomId: () => string;
|
|
17
|
+
sleep: (ms: number) => Promise<void>;
|
|
18
|
+
now: () => number;
|
|
19
|
+
log: (message: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function runLogin(overrides?: Partial<LoginDependencies>): Promise<void>;
|
package/dist/commands/login.js
CHANGED
|
@@ -1,49 +1,143 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
3
|
import open from "open";
|
|
4
|
-
import { getAuthToken,
|
|
5
|
-
|
|
4
|
+
import { getAuthToken, getCreatorToken, saveCreatorToken, saveLoginSession, } from "../lib/auth.js";
|
|
5
|
+
import { getCreatorApiUrl, getGatewayUrl } from "../lib/config.js";
|
|
6
6
|
const POLL_INTERVAL_MS = 2000;
|
|
7
7
|
const POLL_TIMEOUT_MS = 120000;
|
|
8
8
|
export const loginCommand = new Command("login")
|
|
9
9
|
.description("Sign in to Summer Engine via your browser")
|
|
10
|
+
.option("--creator", "Connect a separately scoped Summercraft creator token for publishing")
|
|
10
11
|
.option("--force", "Force re-authentication even if already logged in")
|
|
11
12
|
.action(async (opts) => {
|
|
13
|
+
if (opts.creator) {
|
|
14
|
+
const existingCreator = await getCreatorToken();
|
|
15
|
+
if (existingCreator && !opts.force) {
|
|
16
|
+
console.log("A creator token is already connected. Use --creator --force to replace it.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await runCreatorLogin();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
12
22
|
const existing = await getAuthToken();
|
|
13
23
|
if (existing && !opts.force) {
|
|
14
24
|
console.log("Already logged in. Use --force to re-authenticate.");
|
|
15
25
|
return;
|
|
16
26
|
}
|
|
17
|
-
await
|
|
27
|
+
await runLogin();
|
|
18
28
|
});
|
|
19
|
-
async function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
async function readHiddenLine(prompt) {
|
|
30
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
31
|
+
process.stdout.write(prompt);
|
|
32
|
+
let value = "";
|
|
33
|
+
for await (const chunk of process.stdin)
|
|
34
|
+
value += String(chunk);
|
|
35
|
+
return value.trim();
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(prompt);
|
|
38
|
+
const input = process.stdin;
|
|
39
|
+
const wasRaw = input.isRaw;
|
|
40
|
+
input.setRawMode(true);
|
|
41
|
+
input.resume();
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
let value = "";
|
|
44
|
+
const finish = (error) => {
|
|
45
|
+
input.off("data", onData);
|
|
46
|
+
input.setRawMode(Boolean(wasRaw));
|
|
47
|
+
input.pause();
|
|
48
|
+
process.stdout.write("\n");
|
|
49
|
+
if (error)
|
|
50
|
+
reject(error);
|
|
51
|
+
else
|
|
52
|
+
resolve(value.trim());
|
|
53
|
+
};
|
|
54
|
+
const onData = (chunk) => {
|
|
55
|
+
for (const char of String(chunk)) {
|
|
56
|
+
if (char === "\u0003") {
|
|
57
|
+
finish(new Error("Creator token entry cancelled."));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (char === "\r" || char === "\n" || char === "\u0004") {
|
|
61
|
+
finish();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (char === "\u007f" || char === "\b") {
|
|
65
|
+
value = value.slice(0, -1);
|
|
66
|
+
}
|
|
67
|
+
else if (char >= " ") {
|
|
68
|
+
value += char;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
input.on("data", onData);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const defaultCreatorLoginDependencies = {
|
|
76
|
+
openUrl: open,
|
|
77
|
+
readSecret: readHiddenLine,
|
|
78
|
+
log: console.log,
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The browser session mints the token; the token is pasted once into a hidden
|
|
82
|
+
* prompt. This deliberately does not replace the core Summer login JWT.
|
|
83
|
+
*/
|
|
84
|
+
export async function runCreatorLogin(overrides = {}) {
|
|
85
|
+
const deps = { ...defaultCreatorLoginDependencies, ...overrides };
|
|
86
|
+
const settingsUrl = `${await getCreatorApiUrl()}/creator/settings/tokens`;
|
|
87
|
+
deps.log(`Opening creator token settings: ${settingsUrl}`);
|
|
88
|
+
deps.log('Mint a token with the exact "publish" scope, copy its one-time sc_ value, then return here.');
|
|
24
89
|
try {
|
|
25
|
-
await
|
|
90
|
+
await deps.openUrl(settingsUrl);
|
|
26
91
|
}
|
|
27
92
|
catch {
|
|
28
|
-
|
|
29
|
-
console.log("");
|
|
93
|
+
deps.log("Could not open the browser. Copy the URL above and open it manually.");
|
|
30
94
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
95
|
+
const token = await deps.readSecret("Creator token (input hidden): ");
|
|
96
|
+
await saveCreatorToken(token);
|
|
97
|
+
deps.log("Creator publishing connected. Your core Summer login token was not changed.");
|
|
98
|
+
}
|
|
99
|
+
const defaultDependencies = {
|
|
100
|
+
fetch,
|
|
101
|
+
openUrl: open,
|
|
102
|
+
randomId: randomUUID,
|
|
103
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
104
|
+
now: Date.now,
|
|
105
|
+
log: console.log,
|
|
106
|
+
};
|
|
107
|
+
export async function runLogin(overrides = {}) {
|
|
108
|
+
const deps = { ...defaultDependencies, ...overrides };
|
|
109
|
+
const gatewayUrl = await getGatewayUrl();
|
|
110
|
+
const sessionId = deps.randomId();
|
|
111
|
+
const loginUrl = `${gatewayUrl}/login?cli_session=${encodeURIComponent(sessionId)}`;
|
|
112
|
+
deps.log("Sign in at: " + loginUrl);
|
|
113
|
+
deps.log("");
|
|
114
|
+
try {
|
|
115
|
+
await deps.openUrl(loginUrl);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
deps.log("Could not open browser. Copy the URL above and open it manually.");
|
|
119
|
+
deps.log("");
|
|
120
|
+
}
|
|
121
|
+
deps.log("Waiting for authentication...");
|
|
122
|
+
const pollUrl = `${gatewayUrl}/api/auth/cli-login?session=${encodeURIComponent(sessionId)}`;
|
|
123
|
+
const startTime = deps.now();
|
|
34
124
|
let lastError = null;
|
|
35
|
-
while (
|
|
36
|
-
await sleep(POLL_INTERVAL_MS);
|
|
125
|
+
while (deps.now() - startTime < POLL_TIMEOUT_MS) {
|
|
126
|
+
await deps.sleep(POLL_INTERVAL_MS);
|
|
37
127
|
try {
|
|
38
|
-
const res = await fetch(pollUrl, {
|
|
128
|
+
const res = await deps.fetch(pollUrl, {
|
|
39
129
|
signal: AbortSignal.timeout(5000),
|
|
40
130
|
});
|
|
41
131
|
if (!res.ok) {
|
|
42
132
|
if (res.status === 503) {
|
|
43
|
-
lastError =
|
|
133
|
+
lastError =
|
|
134
|
+
"The login service is unavailable (Redis/auth is not configured).";
|
|
135
|
+
}
|
|
136
|
+
else if (res.status >= 400 && res.status < 500) {
|
|
137
|
+
lastError = `The login request was rejected (${res.status}).`;
|
|
44
138
|
}
|
|
45
139
|
else if (res.status >= 500) {
|
|
46
|
-
lastError = `
|
|
140
|
+
lastError = `The login service failed (${res.status}).`;
|
|
47
141
|
}
|
|
48
142
|
continue;
|
|
49
143
|
}
|
|
@@ -51,28 +145,21 @@ async function doLogin() {
|
|
|
51
145
|
if (data.status === "pending")
|
|
52
146
|
continue;
|
|
53
147
|
if (data.status === "complete" && data.token) {
|
|
54
|
-
await
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
148
|
+
await saveLoginSession({
|
|
149
|
+
token: data.token,
|
|
150
|
+
cloudToken: data.cloudToken,
|
|
151
|
+
user: data.user,
|
|
152
|
+
scopes: data.scopes,
|
|
153
|
+
cloudScopes: data.cloudScopes,
|
|
154
|
+
});
|
|
155
|
+
deps.log(`\nLogged in as ${data.user?.email || "unknown"}`);
|
|
62
156
|
return;
|
|
63
157
|
}
|
|
158
|
+
lastError = "The login service returned an incomplete authentication result.";
|
|
64
159
|
}
|
|
65
160
|
catch (err) {
|
|
66
161
|
lastError = err instanceof Error ? err.message : "Network error";
|
|
67
162
|
}
|
|
68
163
|
}
|
|
69
|
-
|
|
70
|
-
if (lastError) {
|
|
71
|
-
console.error(`Last error: ${lastError}`);
|
|
72
|
-
}
|
|
73
|
-
console.error("\nDid you click \"Yes, Sign In\" in the browser after opening the URL?");
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
function sleep(ms) {
|
|
77
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
164
|
+
throw new Error(`Login timed out after 120 seconds.${lastError ? ` Last error: ${lastError}` : ""} Recovery: run "summer login" again, complete "Yes, Sign In" in the browser, and return to the terminal within two minutes.`);
|
|
78
165
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { getSummerDir } from "../lib/auth.js";
|
|
2
|
+
import { clearAuthCredentials } from "../lib/auth.js";
|
|
5
3
|
export const logoutCommand = new Command("logout")
|
|
6
4
|
.description("Sign out and clear stored auth tokens")
|
|
7
5
|
.action(async () => {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
let cleared = false;
|
|
11
|
-
for (const file of files) {
|
|
12
|
-
try {
|
|
13
|
-
await unlink(join(dir, file));
|
|
14
|
-
cleared = true;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
// File doesn't exist, that's fine
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (cleared) {
|
|
6
|
+
const removed = await clearAuthCredentials();
|
|
7
|
+
if (removed > 0) {
|
|
21
8
|
console.log("Logged out. Auth tokens cleared.");
|
|
22
9
|
}
|
|
23
10
|
else {
|