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
package/dist/commands/create.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
|
+
import { fetchRemoteTemplates, cloneTemplate, matchTemplate } from "../lib/remote-templates.js";
|
|
4
5
|
const BUILTIN_TEMPLATES = [
|
|
5
6
|
{
|
|
6
7
|
name: "empty",
|
|
@@ -22,30 +23,67 @@ const BUILTIN_TEMPLATES = [
|
|
|
22
23
|
},
|
|
23
24
|
];
|
|
24
25
|
export const createCommand = new Command("create")
|
|
25
|
-
.description("Create a new Summer Engine project from
|
|
26
|
-
.argument("<template>", `Template
|
|
27
|
-
.argument("[name]", "Project directory name (defaults to template
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.error(`Unknown template: ${templateName}`);
|
|
32
|
-
console.log("\nAvailable templates:");
|
|
33
|
-
for (const t of BUILTIN_TEMPLATES) {
|
|
34
|
-
console.log(` ${t.name.padEnd(12)} ${t.description}`);
|
|
35
|
-
}
|
|
36
|
-
console.log("\nMore templates coming soon at github.com/summerengine/templates");
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
26
|
+
.description("Create a new Summer Engine project. Built-in templates work offline; community templates clone from github.com/SummerEngine/template-*.")
|
|
27
|
+
.argument("<template>", `Template slug. Built-in: ${BUILTIN_TEMPLATES.map((t) => t.name).join(", ")}. Or any community slug — see "summer list templates".`)
|
|
28
|
+
.argument("[name]", "Project directory name (defaults to template slug)")
|
|
29
|
+
.option("--keep-git", "Keep the upstream .git directory after cloning a remote template (default: detach so you start fresh)")
|
|
30
|
+
.action(async (templateName, projectName, options) => {
|
|
31
|
+
const builtin = BUILTIN_TEMPLATES.find((t) => t.name === templateName);
|
|
39
32
|
const dirName = projectName || templateName;
|
|
40
33
|
const fullPath = resolve(dirName);
|
|
41
34
|
if (existsSync(fullPath)) {
|
|
42
35
|
console.error(`Directory already exists: ${fullPath}`);
|
|
43
36
|
process.exit(1);
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
if (builtin) {
|
|
39
|
+
console.log(`Creating project from built-in '${templateName}' template...`);
|
|
40
|
+
mkdirSync(fullPath, { recursive: true });
|
|
41
|
+
builtin.generate(fullPath, dirName);
|
|
42
|
+
console.log(`\nProject created at ${fullPath}`);
|
|
43
|
+
console.log("\nNext steps:");
|
|
44
|
+
console.log(` summer run ${dirName}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Fall back to remote template lookup
|
|
48
|
+
let remote;
|
|
49
|
+
try {
|
|
50
|
+
const all = await fetchRemoteTemplates();
|
|
51
|
+
remote = matchTemplate(templateName, all);
|
|
52
|
+
if (!remote) {
|
|
53
|
+
console.error(`No template matches '${templateName}'.\n`);
|
|
54
|
+
console.error("Built-in templates:");
|
|
55
|
+
for (const t of BUILTIN_TEMPLATES) {
|
|
56
|
+
console.error(` ${t.name.padEnd(16)} ${t.description}`);
|
|
57
|
+
}
|
|
58
|
+
if (all.length > 0) {
|
|
59
|
+
console.error("\nCommunity templates from github.com/SummerEngine:");
|
|
60
|
+
for (const t of all.slice(0, 10)) {
|
|
61
|
+
console.error(` ${t.slug.padEnd(40)} ${t.description}`);
|
|
62
|
+
}
|
|
63
|
+
if (all.length > 10)
|
|
64
|
+
console.error(` ...and ${all.length - 10} more — run "summer list templates"`);
|
|
65
|
+
}
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(`Could not reach GitHub to look up community templates: ${err.message}`);
|
|
71
|
+
console.error("\nBuilt-in templates that work offline:");
|
|
72
|
+
for (const t of BUILTIN_TEMPLATES) {
|
|
73
|
+
console.error(` ${t.name.padEnd(16)} ${t.description}`);
|
|
74
|
+
}
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
console.log(`Cloning community template '${remote.repo}' (${remote.url}) ...`);
|
|
78
|
+
try {
|
|
79
|
+
cloneTemplate(remote, { targetDir: fullPath, detach: !options.keepGit });
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error(err.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
48
85
|
console.log(`\nProject created at ${fullPath}`);
|
|
86
|
+
console.log("Source: " + remote.url);
|
|
49
87
|
console.log("\nNext steps:");
|
|
50
88
|
console.log(` summer run ${dirName}`);
|
|
51
89
|
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { withEngineClient } from "../lib/with-engine.js";
|
|
3
|
+
import { outputResult, outputError, isJsonMode } from "../lib/format.js";
|
|
4
|
+
export const debugCommand = new Command("debug").description("Debug and run commands (requires running engine)");
|
|
5
|
+
debugCommand
|
|
6
|
+
.command("diagnostics")
|
|
7
|
+
.description("Quick overview of all errors and warnings from editor console and debugger")
|
|
8
|
+
.action(async (_opts, cmd) => {
|
|
9
|
+
const fmt = isJsonMode(cmd);
|
|
10
|
+
try {
|
|
11
|
+
const result = await withEngineClient((c) => c.getDiagnostics());
|
|
12
|
+
outputResult(result, fmt);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
debugCommand
|
|
19
|
+
.command("play [scene]")
|
|
20
|
+
.description("Start running the game (optionally a specific scene)")
|
|
21
|
+
.action(async (scene, _opts, cmd) => {
|
|
22
|
+
const fmt = isJsonMode(cmd);
|
|
23
|
+
try {
|
|
24
|
+
const result = await withEngineClient((c) => c.play(scene));
|
|
25
|
+
outputResult(result, fmt);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
debugCommand
|
|
32
|
+
.command("stop")
|
|
33
|
+
.description("Stop the running game")
|
|
34
|
+
.action(async (_opts, cmd) => {
|
|
35
|
+
const fmt = isJsonMode(cmd);
|
|
36
|
+
try {
|
|
37
|
+
const result = await withEngineClient((c) => c.stop());
|
|
38
|
+
outputResult(result, fmt);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
debugCommand
|
|
45
|
+
.command("console")
|
|
46
|
+
.description("Read recent messages from the editor Output panel")
|
|
47
|
+
.option("--max-lines <n>", "Max lines to return", "100")
|
|
48
|
+
.option("--filter <string>", "Only return lines containing this string")
|
|
49
|
+
.option("--type <type>", "Filter by type: error, warning, std, editor")
|
|
50
|
+
.action(async (opts, cmd) => {
|
|
51
|
+
const fmt = isJsonMode(cmd);
|
|
52
|
+
try {
|
|
53
|
+
const op = {
|
|
54
|
+
op: "GetConsoleOutput",
|
|
55
|
+
max_lines: parseInt(opts.maxLines, 10),
|
|
56
|
+
};
|
|
57
|
+
if (opts.filter)
|
|
58
|
+
op.filter = opts.filter;
|
|
59
|
+
if (opts.type)
|
|
60
|
+
op.type = opts.type;
|
|
61
|
+
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
62
|
+
outputResult(result, fmt);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
debugCommand
|
|
69
|
+
.command("errors")
|
|
70
|
+
.description("Read runtime errors from the debugger")
|
|
71
|
+
.option("--max-errors <n>", "Max errors to return", "50")
|
|
72
|
+
.option("--stack", "Include stack traces")
|
|
73
|
+
.action(async (opts, cmd) => {
|
|
74
|
+
const fmt = isJsonMode(cmd);
|
|
75
|
+
try {
|
|
76
|
+
const op = {
|
|
77
|
+
op: "GetDebuggerErrors",
|
|
78
|
+
max_errors: parseInt(opts.maxErrors, 10),
|
|
79
|
+
};
|
|
80
|
+
if (opts.stack !== undefined)
|
|
81
|
+
op.include_stack = opts.stack;
|
|
82
|
+
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
83
|
+
outputResult(result, fmt);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
debugCommand
|
|
90
|
+
.command("script-errors <path>")
|
|
91
|
+
.description("Check a GDScript file for parse/compile errors (e.g. res://scripts/player.gd)")
|
|
92
|
+
.action(async (path, _opts, cmd) => {
|
|
93
|
+
const fmt = isJsonMode(cmd);
|
|
94
|
+
try {
|
|
95
|
+
const result = await withEngineClient((c) => c.getScriptErrors(path));
|
|
96
|
+
outputResult(result, fmt);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
debugCommand
|
|
103
|
+
.command("clear")
|
|
104
|
+
.description("Clear the editor Output panel")
|
|
105
|
+
.action(async (_opts, cmd) => {
|
|
106
|
+
const fmt = isJsonMode(cmd);
|
|
107
|
+
try {
|
|
108
|
+
const result = await withEngineClient((c) => c.executeOps([{ op: "ClearConsoleOutput" }]));
|
|
109
|
+
outputResult(result, fmt);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
debugCommand
|
|
116
|
+
.command("running")
|
|
117
|
+
.description("Check if the game is currently running")
|
|
118
|
+
.action(async (_opts, cmd) => {
|
|
119
|
+
const fmt = isJsonMode(cmd);
|
|
120
|
+
try {
|
|
121
|
+
const result = await withEngineClient((c) => c.executeOps([{ op: "IsGameRunning" }]));
|
|
122
|
+
outputResult(result, fmt);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
126
|
+
}
|
|
127
|
+
});
|
package/dist/commands/list.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readdirSync, existsSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { fetchRemoteTemplates } from "../lib/remote-templates.js";
|
|
4
5
|
export const listCommand = new Command("list")
|
|
5
6
|
.description("List available templates or local projects")
|
|
6
7
|
.argument("<what>", "'templates' or 'projects'")
|
|
7
8
|
.action(async (what) => {
|
|
8
9
|
if (what === "templates") {
|
|
9
|
-
listTemplates();
|
|
10
|
+
await listTemplates();
|
|
10
11
|
}
|
|
11
12
|
else if (what === "projects") {
|
|
12
13
|
listProjects();
|
|
@@ -16,12 +17,30 @@ export const listCommand = new Command("list")
|
|
|
16
17
|
process.exit(1);
|
|
17
18
|
}
|
|
18
19
|
});
|
|
19
|
-
function listTemplates() {
|
|
20
|
-
console.log("Built-in
|
|
20
|
+
async function listTemplates() {
|
|
21
|
+
console.log("Built-in templates (no download required):\n");
|
|
21
22
|
console.log(" empty Empty 3D project with just a root node");
|
|
22
23
|
console.log(" 3d-basic 3D scene with camera, light, and floor");
|
|
23
|
-
console.log("\
|
|
24
|
-
|
|
24
|
+
console.log("\nCommunity templates from github.com/SummerEngine:\n");
|
|
25
|
+
try {
|
|
26
|
+
const remote = await fetchRemoteTemplates();
|
|
27
|
+
if (remote.length === 0) {
|
|
28
|
+
console.log(" (none yet)");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const padTo = Math.max(...remote.map((t) => t.slug.length), 16);
|
|
32
|
+
for (const t of remote) {
|
|
33
|
+
const desc = t.description || `(no description) ${t.url}`;
|
|
34
|
+
console.log(` ${t.slug.padEnd(padTo)} ${desc}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.log(` (could not reach GitHub: ${err.message})`);
|
|
40
|
+
console.log(" Built-ins above still work offline.");
|
|
41
|
+
}
|
|
42
|
+
console.log("\nCreate a project: summer create <name> [project-dir]");
|
|
43
|
+
console.log("Example: summer create 3d-third-person-controller my-game");
|
|
25
44
|
}
|
|
26
45
|
function listProjects() {
|
|
27
46
|
const cwd = process.cwd();
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { withEngineClient } from "../lib/with-engine.js";
|
|
5
|
+
import { outputResult, outputError, parseValue, readJsonArg, isJsonMode, } from "../lib/format.js";
|
|
6
|
+
async function readMainSceneFromProject(projectPath) {
|
|
7
|
+
if (!projectPath)
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const text = await readFile(join(projectPath, "project.godot"), "utf-8");
|
|
11
|
+
const match = text.match(/run\/main_scene="([^"]+)"/);
|
|
12
|
+
return match?.[1] ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export const projectCommand = new Command("project").description("Project context and settings (requires running engine)");
|
|
19
|
+
projectCommand
|
|
20
|
+
.command("context")
|
|
21
|
+
.description("Get project context: health, current scene, main scene, project path")
|
|
22
|
+
.action(async (_opts, cmd) => {
|
|
23
|
+
const fmt = isJsonMode(cmd);
|
|
24
|
+
try {
|
|
25
|
+
const result = await withEngineClient(async (client) => {
|
|
26
|
+
const [health, projectState] = await Promise.all([
|
|
27
|
+
client.health(),
|
|
28
|
+
client.getProjectState(),
|
|
29
|
+
]);
|
|
30
|
+
const healthObj = (health ?? {});
|
|
31
|
+
const mainScene = await readMainSceneFromProject(typeof healthObj.project_path === "string"
|
|
32
|
+
? healthObj.project_path
|
|
33
|
+
: undefined);
|
|
34
|
+
return {
|
|
35
|
+
health,
|
|
36
|
+
project: projectState,
|
|
37
|
+
mainScene,
|
|
38
|
+
guidance: mainScene
|
|
39
|
+
? "Use `summer scene open` with mainScene if no scene is open."
|
|
40
|
+
: "Main scene not found in project.godot. Open a known scene path explicitly.",
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
outputResult(result, fmt);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
projectCommand
|
|
50
|
+
.command("open-main")
|
|
51
|
+
.description("Open the project's main scene from project.godot")
|
|
52
|
+
.action(async (_opts, cmd) => {
|
|
53
|
+
const fmt = isJsonMode(cmd);
|
|
54
|
+
try {
|
|
55
|
+
const result = await withEngineClient(async (client) => {
|
|
56
|
+
const health = (await client.health());
|
|
57
|
+
const projectPath = typeof health.project_path === "string"
|
|
58
|
+
? health.project_path
|
|
59
|
+
: undefined;
|
|
60
|
+
const mainScene = await readMainSceneFromProject(projectPath);
|
|
61
|
+
if (!mainScene) {
|
|
62
|
+
throw new Error("Could not resolve main_scene from project.godot. Run `summer project context` and open a scene explicitly.");
|
|
63
|
+
}
|
|
64
|
+
return client.executeOps([{ op: "OpenScene", path: mainScene }]);
|
|
65
|
+
});
|
|
66
|
+
outputResult(result, fmt);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
projectCommand
|
|
73
|
+
.command("setting <key> <value>")
|
|
74
|
+
.description('Set a project setting (e.g. summer project setting application/config/name "My Game")')
|
|
75
|
+
.action(async (key, value, _opts, cmd) => {
|
|
76
|
+
const fmt = isJsonMode(cmd);
|
|
77
|
+
try {
|
|
78
|
+
const result = await withEngineClient((c) => c.executeOps([
|
|
79
|
+
{ op: "ProjectSetting", key, value: parseValue(value) },
|
|
80
|
+
]));
|
|
81
|
+
outputResult(result, fmt);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
projectCommand
|
|
88
|
+
.command("input-bind <name> <events>")
|
|
89
|
+
.description('Bind input events to an action (e.g. summer project input-bind jump \'[{"type":"key","key":"Space"}]\')')
|
|
90
|
+
.action(async (name, eventsJson, _opts, cmd) => {
|
|
91
|
+
const fmt = isJsonMode(cmd);
|
|
92
|
+
try {
|
|
93
|
+
const events = JSON.parse(eventsJson);
|
|
94
|
+
const result = await withEngineClient((c) => c.executeOps([
|
|
95
|
+
{ op: "InputMapAddAction", name },
|
|
96
|
+
{ op: "InputMapBind", name, events },
|
|
97
|
+
]));
|
|
98
|
+
outputResult(result, fmt);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
projectCommand
|
|
105
|
+
.command("import <url>")
|
|
106
|
+
.description("Download a file from a URL and import into the project")
|
|
107
|
+
.option("--path <path>", "Target path in project (auto-inferred from URL if omitted)")
|
|
108
|
+
.action(async (url, opts, cmd) => {
|
|
109
|
+
const fmt = isJsonMode(cmd);
|
|
110
|
+
try {
|
|
111
|
+
const op = { op: "ImportFromUrl", url };
|
|
112
|
+
if (opts.path)
|
|
113
|
+
op.path = opts.path;
|
|
114
|
+
const result = await withEngineClient((c) => c.executeOps([op]));
|
|
115
|
+
outputResult(result, fmt);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
projectCommand
|
|
122
|
+
.command("import-batch <json>")
|
|
123
|
+
.description('Import multiple files. Pass JSON array of {url, path} or "-" for stdin')
|
|
124
|
+
.action(async (jsonArg, _opts, cmd) => {
|
|
125
|
+
const fmt = isJsonMode(cmd);
|
|
126
|
+
try {
|
|
127
|
+
const imports = (await readJsonArg(jsonArg));
|
|
128
|
+
const result = await withEngineClient((c) => c.executeOps([{ op: "ImportFromUrlBatch", imports }]));
|
|
129
|
+
outputResult(result, fmt);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
outputError(err instanceof Error ? err.message : String(err), fmt);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
projectCommand
|
|
136
|
+
.command("playbook")
|
|
137
|
+
.description("Show the AI agent operating guide for Summer Engine")
|
|
138
|
+
.action(async (_opts, cmd) => {
|
|
139
|
+
const fmt = isJsonMode(cmd);
|
|
140
|
+
const playbook = {
|
|
141
|
+
startupChecklist: [
|
|
142
|
+
"Decide first: file edit or engine operation.",
|
|
143
|
+
"For normal code/content edits (.gd/.tscn/.tres/.ts), use host file-edit tools directly.",
|
|
144
|
+
"Call `summer project context` first.",
|
|
145
|
+
"If no scene is open, call `summer project open-main`.",
|
|
146
|
+
"Call `summer scene tree` before structural edits.",
|
|
147
|
+
"Call `summer scene save` after edits.",
|
|
148
|
+
],
|
|
149
|
+
safeDefaults: [
|
|
150
|
+
"Never guess scene filenames (main.tscn/Main.tscn).",
|
|
151
|
+
"Do not route ordinary code edits through engine ops when direct file tools are available.",
|
|
152
|
+
"Never remove multiple top-level nodes unless user explicitly requests destructive edits.",
|
|
153
|
+
"Never write .tscn/.scn with file write tools; use scene commands.",
|
|
154
|
+
],
|
|
155
|
+
preferredFlow: [
|
|
156
|
+
"summer project context",
|
|
157
|
+
"summer project open-main (if needed)",
|
|
158
|
+
"summer scene tree",
|
|
159
|
+
"edit via summer scene add / summer scene prop / summer scene rprop",
|
|
160
|
+
"summer scene save",
|
|
161
|
+
"summer debug diagnostics",
|
|
162
|
+
],
|
|
163
|
+
recovery: [
|
|
164
|
+
"If you see 'no scene open': run summer project open-main.",
|
|
165
|
+
"If open fails: re-check mainScene from summer project context.",
|
|
166
|
+
"If save fails: verify scene is open and game is not running.",
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
outputResult(playbook, fmt);
|
|
170
|
+
});
|