summer-engine 0.0.1 → 1.0.1
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/README.md +72 -8
- package/dist/bin/postinstall.d.ts +2 -0
- package/dist/bin/postinstall.js +3 -0
- package/dist/bin/summer.d.ts +2 -0
- package/dist/bin/summer.js +34 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.js +99 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +135 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +48 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +75 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +26 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +32 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +69 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.js +187 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +43 -0
- package/dist/lib/api-client.d.ts +17 -0
- package/dist/lib/api-client.js +69 -0
- package/dist/lib/auth.d.ts +13 -0
- package/dist/lib/auth.js +39 -0
- package/dist/lib/banner.d.ts +4 -0
- package/dist/lib/banner.js +122 -0
- package/dist/lib/engine.d.ts +12 -0
- package/dist/lib/engine.js +36 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +40 -0
- package/dist/mcp/tools/asset-tools.d.ts +2 -0
- package/dist/mcp/tools/asset-tools.js +247 -0
- package/dist/mcp/tools/debug-tools.d.ts +2 -0
- package/dist/mcp/tools/debug-tools.js +49 -0
- package/dist/mcp/tools/project-tools.d.ts +2 -0
- package/dist/mcp/tools/project-tools.js +145 -0
- package/dist/mcp/tools/scene-tools.d.ts +2 -0
- package/dist/mcp/tools/scene-tools.js +215 -0
- package/dist/mcp/tools/with-engine.d.ts +10 -0
- package/dist/mcp/tools/with-engine.js +93 -0
- package/package.json +35 -6
- package/skills/3d-lighting/SKILL.md +103 -0
- package/skills/fps-controller/SKILL.md +131 -0
- package/skills/gdscript-patterns/SKILL.md +96 -0
- package/skills/gdscript-patterns/reference.md +55 -0
- package/skills/scene-composition/SKILL.md +108 -0
- package/skills/ui-basics/SKILL.md +121 -0
- package/bin/summer.js +0 -3
package/README.md
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
1
|
# Summer Engine CLI
|
|
2
2
|
|
|
3
|
-
CLI and MCP tools for [Summer Engine](https://summerengine.com)
|
|
3
|
+
CLI and MCP tools for [Summer Engine](https://summerengine.com) — the AI-native game engine.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g summer-engine
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx summer-engine <command>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| `summer install` | Download and install Summer Engine |
|
|
22
|
+
| `summer login` | Sign in via browser (Google/GitHub) |
|
|
23
|
+
| `summer logout` | Clear auth tokens |
|
|
24
|
+
| `summer status` | Check engine status, port, auth state |
|
|
25
|
+
| `summer run [path]` | Launch engine (optionally with a project) |
|
|
26
|
+
| `summer open <path>` | Open a project in the running engine |
|
|
27
|
+
| `summer create <template> [name]` | Create new project from template |
|
|
28
|
+
| `summer list templates` | Show available templates |
|
|
29
|
+
| `summer list projects` | Show local projects |
|
|
30
|
+
| `summer skills list` | Show available skills (best-practice guides) |
|
|
31
|
+
| `summer skills install <name>` | Install a skill for AI agents to use |
|
|
32
|
+
| `summer mcp` | Start MCP server (used by Cursor/Claude Code) |
|
|
6
33
|
|
|
7
34
|
## Quick Start
|
|
8
35
|
|
|
9
36
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
summer install # Download the engine
|
|
38
|
+
summer login # Sign in
|
|
39
|
+
summer create 3d-basic my-game # Create a project
|
|
40
|
+
summer run my-game # Open it in the engine
|
|
14
41
|
```
|
|
15
42
|
|
|
16
43
|
## MCP Integration
|
|
17
44
|
|
|
18
|
-
Use Summer Engine with Cursor, Claude Code, or any MCP-compatible AI tool
|
|
45
|
+
Use Summer Engine with Cursor, Claude Code, or any MCP-compatible AI tool.
|
|
46
|
+
|
|
47
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
19
48
|
|
|
20
49
|
```json
|
|
21
50
|
{
|
|
@@ -28,7 +57,42 @@ Use Summer Engine with Cursor, Claude Code, or any MCP-compatible AI tool:
|
|
|
28
57
|
}
|
|
29
58
|
```
|
|
30
59
|
|
|
31
|
-
|
|
60
|
+
**Claude Code** (`~/.claude/claude_code_config.json`):
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"summer-engine": {
|
|
66
|
+
"command": "npx",
|
|
67
|
+
"args": ["summer-engine", "mcp"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The MCP server exposes 24 focused tools for scene manipulation, debugging, project settings, and asset search — only things AI tools can't do on their own.
|
|
74
|
+
|
|
75
|
+
**Visual feedback** (viewport and game screenshots) is currently available in the Summer Engine app via the built-in Summer Agent. MCP support for screenshots is in development.
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
1. Summer Engine runs a local API server on `localhost:6550`
|
|
80
|
+
2. The MCP server connects to this API via a local auth token (`~/.summer/api-token`)
|
|
81
|
+
3. AI tools call MCP tools, which translate to API requests to the engine
|
|
82
|
+
4. The engine executes operations and returns results
|
|
83
|
+
|
|
84
|
+
The engine must be running for MCP tools to work. If it's not running, tools return a helpful error message.
|
|
85
|
+
|
|
86
|
+
## Templates
|
|
87
|
+
|
|
88
|
+
Built-in templates:
|
|
89
|
+
|
|
90
|
+
- `empty` — Empty 3D project with a root node
|
|
91
|
+
- `3d-basic` — 3D scene with camera, light, and floor
|
|
92
|
+
|
|
93
|
+
More templates coming soon.
|
|
94
|
+
|
|
95
|
+
## Links
|
|
32
96
|
|
|
33
97
|
- [Website](https://summerengine.com)
|
|
34
98
|
- [Documentation](https://summerengine.com/docs)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { mcpCommand } from "../commands/mcp.js";
|
|
5
|
+
import { loginCommand } from "../commands/login.js";
|
|
6
|
+
import { logoutCommand } from "../commands/logout.js";
|
|
7
|
+
import { statusCommand } from "../commands/status.js";
|
|
8
|
+
import { runCommand } from "../commands/run.js";
|
|
9
|
+
import { openCommand } from "../commands/open.js";
|
|
10
|
+
import { installCommand } from "../commands/install.js";
|
|
11
|
+
import { createCommand } from "../commands/create.js";
|
|
12
|
+
import { listCommand } from "../commands/list.js";
|
|
13
|
+
import { skillsCommand } from "../commands/skills.js";
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { version } = require("../../package.json");
|
|
16
|
+
const program = new Command();
|
|
17
|
+
program
|
|
18
|
+
.name("summer")
|
|
19
|
+
.description("CLI and MCP tools for Summer Engine — the AI-native game engine")
|
|
20
|
+
.version(version);
|
|
21
|
+
program.addCommand(installCommand);
|
|
22
|
+
program.addCommand(loginCommand);
|
|
23
|
+
program.addCommand(logoutCommand);
|
|
24
|
+
program.addCommand(statusCommand);
|
|
25
|
+
program.addCommand(runCommand);
|
|
26
|
+
program.addCommand(openCommand);
|
|
27
|
+
program.addCommand(createCommand);
|
|
28
|
+
program.addCommand(listCommand);
|
|
29
|
+
program.addCommand(skillsCommand);
|
|
30
|
+
program.addCommand(mcpCommand);
|
|
31
|
+
program.parseAsync().catch((err) => {
|
|
32
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
|
+
const BUILTIN_TEMPLATES = [
|
|
5
|
+
{
|
|
6
|
+
name: "empty",
|
|
7
|
+
description: "Empty 3D project with just a root node",
|
|
8
|
+
builtin: true,
|
|
9
|
+
generate: (dir, name) => {
|
|
10
|
+
writeFileSync(join(dir, "project.godot"), projectGodot(name, "res://main.tscn"));
|
|
11
|
+
writeFileSync(join(dir, "main.tscn"), emptyScene());
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "3d-basic",
|
|
16
|
+
description: "3D scene with camera, light, and floor",
|
|
17
|
+
builtin: true,
|
|
18
|
+
generate: (dir, name) => {
|
|
19
|
+
writeFileSync(join(dir, "project.godot"), projectGodot(name, "res://main.tscn"));
|
|
20
|
+
writeFileSync(join(dir, "main.tscn"), basicScene3D());
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
export const createCommand = new Command("create")
|
|
25
|
+
.description("Create a new Summer Engine project from a template")
|
|
26
|
+
.argument("<template>", `Template name (${BUILTIN_TEMPLATES.map((t) => t.name).join(", ")})`)
|
|
27
|
+
.argument("[name]", "Project directory name (defaults to template name)")
|
|
28
|
+
.action(async (templateName, projectName) => {
|
|
29
|
+
const template = BUILTIN_TEMPLATES.find((t) => t.name === templateName);
|
|
30
|
+
if (!template) {
|
|
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
|
+
}
|
|
39
|
+
const dirName = projectName || templateName;
|
|
40
|
+
const fullPath = resolve(dirName);
|
|
41
|
+
if (existsSync(fullPath)) {
|
|
42
|
+
console.error(`Directory already exists: ${fullPath}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log(`Creating project from '${templateName}' template...`);
|
|
46
|
+
mkdirSync(fullPath, { recursive: true });
|
|
47
|
+
template.generate(fullPath, dirName);
|
|
48
|
+
console.log(`\nProject created at ${fullPath}`);
|
|
49
|
+
console.log("\nNext steps:");
|
|
50
|
+
console.log(` summer run ${dirName}`);
|
|
51
|
+
});
|
|
52
|
+
function projectGodot(name, mainScene) {
|
|
53
|
+
return `; Summer Engine Project
|
|
54
|
+
; Generated by summer-engine CLI
|
|
55
|
+
|
|
56
|
+
[application]
|
|
57
|
+
|
|
58
|
+
config/name="${name}"
|
|
59
|
+
run/main_scene="${mainScene}"
|
|
60
|
+
config/features=PackedStringArray("4.5")
|
|
61
|
+
|
|
62
|
+
[rendering]
|
|
63
|
+
|
|
64
|
+
renderer/rendering_method="forward_plus"
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
function emptyScene() {
|
|
68
|
+
return `[gd_scene format=3]
|
|
69
|
+
|
|
70
|
+
[node name="World" type="Node3D"]
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
function basicScene3D() {
|
|
74
|
+
return `[gd_scene load_steps=4 format=3]
|
|
75
|
+
|
|
76
|
+
[sub_resource type="BoxMesh" id="BoxMesh_floor"]
|
|
77
|
+
size = Vector3(20, 0.2, 20)
|
|
78
|
+
|
|
79
|
+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
|
|
80
|
+
albedo_color = Color(0.4, 0.45, 0.4, 1)
|
|
81
|
+
|
|
82
|
+
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
|
83
|
+
|
|
84
|
+
[node name="World" type="Node3D"]
|
|
85
|
+
|
|
86
|
+
[node name="Camera3D" type="Camera3D" parent="."]
|
|
87
|
+
transform = Transform3D(1, 0, 0, 0, 0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 5, 10)
|
|
88
|
+
|
|
89
|
+
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
|
90
|
+
transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 8, 0)
|
|
91
|
+
shadow_enabled = true
|
|
92
|
+
|
|
93
|
+
[node name="Floor" type="MeshInstance3D" parent="."]
|
|
94
|
+
mesh = SubResource("BoxMesh_floor")
|
|
95
|
+
surface_material_override/0 = SubResource("StandardMaterial3D_floor")
|
|
96
|
+
|
|
97
|
+
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { createWriteStream, existsSync, mkdirSync } from "fs";
|
|
4
|
+
import { pipeline } from "stream/promises";
|
|
5
|
+
import { Readable } from "stream";
|
|
6
|
+
import { tmpdir, platform } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
const RELEASES_URL = "https://www.summerengine.com/api/desktop/releases";
|
|
9
|
+
export const installCommand = new Command("install")
|
|
10
|
+
.description("Download and install Summer Engine")
|
|
11
|
+
.option("--path <dir>", "Custom install directory")
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const os = platform();
|
|
14
|
+
if (os !== "darwin" && os !== "win32") {
|
|
15
|
+
console.error("Linux support is coming soon.\n" +
|
|
16
|
+
"For now, download from: https://summerengine.com/download");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
console.log("Fetching latest release info...");
|
|
20
|
+
let releases;
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(RELEASES_URL, { signal: AbortSignal.timeout(10000) });
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`HTTP ${res.status}`);
|
|
25
|
+
releases = (await res.json());
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error("Could not fetch release info. Download manually from:\n" +
|
|
29
|
+
" https://summerengine.com/download");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
if (os === "darwin") {
|
|
33
|
+
await installMac(releases, opts.path);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await installWindows(releases, opts.path);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
async function installMac(releases, customPath) {
|
|
40
|
+
const info = releases.latest.macos;
|
|
41
|
+
if (!info) {
|
|
42
|
+
console.error("No macOS release found.");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log(`Latest version: ${info.version}`);
|
|
46
|
+
const destApp = customPath || "/Applications/Summer.app";
|
|
47
|
+
if (existsSync(destApp)) {
|
|
48
|
+
console.log(`Summer Engine already installed at ${destApp}`);
|
|
49
|
+
console.log("Updating to latest version...");
|
|
50
|
+
}
|
|
51
|
+
const dmgPath = join(tmpdir(), `Summer-v${info.version}.dmg`);
|
|
52
|
+
console.log(`Downloading Summer Engine v${info.version} (~145MB)...`);
|
|
53
|
+
await downloadFile(info.dmg_url, dmgPath);
|
|
54
|
+
console.log("Mounting DMG...");
|
|
55
|
+
const mountOutput = execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -noautoopen`, {
|
|
56
|
+
encoding: "utf-8",
|
|
57
|
+
});
|
|
58
|
+
const mountPoint = mountOutput
|
|
59
|
+
.split("\n")
|
|
60
|
+
.filter((line) => line.includes("/Volumes/"))
|
|
61
|
+
.map((line) => line.trim().split("\t").pop()?.trim())
|
|
62
|
+
.find(Boolean);
|
|
63
|
+
if (!mountPoint) {
|
|
64
|
+
console.error("Failed to find mount point. Install manually from the DMG.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
console.log("Installing to " + destApp + "...");
|
|
69
|
+
execSync(`rm -rf "${destApp}"`, { stdio: "ignore" });
|
|
70
|
+
execSync(`cp -R "${mountPoint}/Summer.app" "${destApp}"`);
|
|
71
|
+
console.log("Done!");
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
execSync(`hdiutil detach "${mountPoint}" -quiet`, { stdio: "ignore" });
|
|
75
|
+
try {
|
|
76
|
+
execSync(`rm "${dmgPath}"`, { stdio: "ignore" });
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
console.log(`\nSummer Engine v${info.version} installed to ${destApp}`);
|
|
81
|
+
console.log("\nNext steps:");
|
|
82
|
+
console.log(" summer login # Sign in to your account");
|
|
83
|
+
console.log(" summer run # Launch the engine");
|
|
84
|
+
}
|
|
85
|
+
async function installWindows(releases, customPath) {
|
|
86
|
+
const info = releases.latest.windows;
|
|
87
|
+
if (!info) {
|
|
88
|
+
console.error("No Windows release found.");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
console.log(`Latest version: ${info.version}`);
|
|
92
|
+
const exePath = join(tmpdir(), `Summer-v${info.version}.exe`);
|
|
93
|
+
console.log(`Downloading Summer Engine v${info.version}...`);
|
|
94
|
+
await downloadFile(info.url, exePath);
|
|
95
|
+
console.log("Running installer...");
|
|
96
|
+
try {
|
|
97
|
+
execSync(`"${exePath}" /S${customPath ? ` /D=${customPath}` : ""}`, {
|
|
98
|
+
stdio: "inherit",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
console.error("Installer failed. Try running it manually:\n " + exePath);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
execSync(`del "${exePath}"`, { stdio: "ignore" });
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
console.log(`\nSummer Engine v${info.version} installed!`);
|
|
110
|
+
console.log("\nNext steps:");
|
|
111
|
+
console.log(" summer login # Sign in to your account");
|
|
112
|
+
console.log(" summer run # Launch the engine");
|
|
113
|
+
}
|
|
114
|
+
async function downloadFile(url, dest) {
|
|
115
|
+
const res = await fetch(url);
|
|
116
|
+
if (!res.ok || !res.body) {
|
|
117
|
+
throw new Error(`Download failed: HTTP ${res.status}`);
|
|
118
|
+
}
|
|
119
|
+
const dir = join(dest, "..");
|
|
120
|
+
if (!existsSync(dir))
|
|
121
|
+
mkdirSync(dir, { recursive: true });
|
|
122
|
+
const fileStream = createWriteStream(dest);
|
|
123
|
+
const readable = Readable.fromWeb(res.body);
|
|
124
|
+
let downloaded = 0;
|
|
125
|
+
const total = parseInt(res.headers.get("content-length") || "0", 10);
|
|
126
|
+
readable.on("data", (chunk) => {
|
|
127
|
+
downloaded += chunk.length;
|
|
128
|
+
if (total > 0) {
|
|
129
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
130
|
+
process.stdout.write(`\r ${pct}% (${(downloaded / 1024 / 1024).toFixed(1)}MB)`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
await pipeline(readable, fileStream);
|
|
134
|
+
process.stdout.write("\n");
|
|
135
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
export const listCommand = new Command("list")
|
|
5
|
+
.description("List available templates or local projects")
|
|
6
|
+
.argument("<what>", "'templates' or 'projects'")
|
|
7
|
+
.action(async (what) => {
|
|
8
|
+
if (what === "templates") {
|
|
9
|
+
listTemplates();
|
|
10
|
+
}
|
|
11
|
+
else if (what === "projects") {
|
|
12
|
+
listProjects();
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.error(`Unknown: '${what}'. Use 'templates' or 'projects'.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
function listTemplates() {
|
|
20
|
+
console.log("Built-in Templates:\n");
|
|
21
|
+
console.log(" empty Empty 3D project with just a root node");
|
|
22
|
+
console.log(" 3d-basic 3D scene with camera, light, and floor");
|
|
23
|
+
console.log("\nCreate a project: summer create <template> [name]");
|
|
24
|
+
console.log("\nMore templates coming soon at github.com/summerengine/templates");
|
|
25
|
+
}
|
|
26
|
+
function listProjects() {
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const entries = readdirSync(cwd, { withFileTypes: true });
|
|
29
|
+
const projects = [];
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
const projectFile = join(cwd, entry.name, "project.godot");
|
|
33
|
+
if (existsSync(projectFile)) {
|
|
34
|
+
projects.push(entry.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (projects.length === 0) {
|
|
39
|
+
console.log("No Summer Engine / Godot projects found in current directory.");
|
|
40
|
+
console.log("\nCreate one: summer create 3d-basic my-game");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log(`Projects in ${cwd}:\n`);
|
|
44
|
+
for (const p of projects) {
|
|
45
|
+
console.log(` ${p}/`);
|
|
46
|
+
}
|
|
47
|
+
console.log(`\nOpen a project: summer run <name>`);
|
|
48
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import { getAuthToken, saveAuthToken, saveUserInfo } from "../lib/auth.js";
|
|
5
|
+
const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
|
|
6
|
+
const POLL_INTERVAL_MS = 2000;
|
|
7
|
+
const POLL_TIMEOUT_MS = 120000;
|
|
8
|
+
export const loginCommand = new Command("login")
|
|
9
|
+
.description("Sign in to Summer Engine via your browser")
|
|
10
|
+
.option("--force", "Force re-authentication even if already logged in")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const existing = await getAuthToken();
|
|
13
|
+
if (existing && !opts.force) {
|
|
14
|
+
console.log("Already logged in. Use --force to re-authenticate.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await doLogin();
|
|
18
|
+
});
|
|
19
|
+
async function doLogin() {
|
|
20
|
+
const sessionId = randomUUID();
|
|
21
|
+
const loginUrl = `${GATEWAY_URL}/login?cli_session=${sessionId}`;
|
|
22
|
+
console.log("Sign in at: " + loginUrl);
|
|
23
|
+
console.log("");
|
|
24
|
+
try {
|
|
25
|
+
await open(loginUrl);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
console.log("Could not open browser. Copy the URL above and open it manually.");
|
|
29
|
+
console.log("");
|
|
30
|
+
}
|
|
31
|
+
console.log("Waiting for authentication...");
|
|
32
|
+
const pollUrl = `${GATEWAY_URL}/api/auth/cli-login?session=${sessionId}`;
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
let lastError = null;
|
|
35
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
36
|
+
await sleep(POLL_INTERVAL_MS);
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(pollUrl, {
|
|
39
|
+
signal: AbortSignal.timeout(5000),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
if (res.status === 503) {
|
|
43
|
+
lastError = "Server not ready (Redis/auth not configured). Try again later.";
|
|
44
|
+
}
|
|
45
|
+
else if (res.status >= 500) {
|
|
46
|
+
lastError = `Server error (${res.status}). Try again later.`;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const data = (await res.json());
|
|
51
|
+
if (data.status === "pending")
|
|
52
|
+
continue;
|
|
53
|
+
if (data.status === "complete" && data.token) {
|
|
54
|
+
await saveAuthToken(data.token);
|
|
55
|
+
if (data.user) {
|
|
56
|
+
await saveUserInfo(data.user);
|
|
57
|
+
}
|
|
58
|
+
console.log(`\nLogged in as ${data.user?.email || "unknown"}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
lastError = err instanceof Error ? err.message : "Network error";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.error("\nLogin timed out. Please try again.");
|
|
67
|
+
if (lastError) {
|
|
68
|
+
console.error(`Last error: ${lastError}`);
|
|
69
|
+
}
|
|
70
|
+
console.error("\nDid you click \"Yes, Sign In\" in the browser after opening the URL?");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
function sleep(ms) {
|
|
74
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { unlink } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { getSummerDir } from "../lib/auth.js";
|
|
5
|
+
export const logoutCommand = new Command("logout")
|
|
6
|
+
.description("Sign out and clear stored auth tokens")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
const dir = getSummerDir();
|
|
9
|
+
const files = ["auth-token", "user.json"];
|
|
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) {
|
|
21
|
+
console.log("Logged out. Auth tokens cleared.");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log("Already logged out (no tokens found).");
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { startMcpServer } from "../mcp/server.js";
|
|
3
|
+
export const mcpCommand = new Command("mcp")
|
|
4
|
+
.description("Start the MCP server for AI tool integration (Cursor, Claude Code, etc.)")
|
|
5
|
+
.action(async () => {
|
|
6
|
+
await startMcpServer();
|
|
7
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { getApiPort, checkEngineHealth } from "../lib/engine.js";
|
|
5
|
+
export const openCommand = new Command("open")
|
|
6
|
+
.description("Open a project in Summer Engine")
|
|
7
|
+
.argument("<path>", "Path to project directory (must contain project.godot)")
|
|
8
|
+
.action(async (projectPath) => {
|
|
9
|
+
const fullPath = resolve(projectPath);
|
|
10
|
+
if (!existsSync(fullPath)) {
|
|
11
|
+
console.error(`Directory not found: ${fullPath}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
if (!existsSync(`${fullPath}/project.godot`)) {
|
|
15
|
+
console.error(`No project.godot found in ${fullPath}\n` +
|
|
16
|
+
"This doesn't look like a Godot/Summer Engine project.\n" +
|
|
17
|
+
"Create one with: summer create 3d-basic my-game");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const port = await getApiPort();
|
|
21
|
+
const health = await checkEngineHealth(port);
|
|
22
|
+
if (!health) {
|
|
23
|
+
console.log("Engine not running. Launching with this project...");
|
|
24
|
+
// Import dynamically to avoid circular deps
|
|
25
|
+
const { runCommand } = await import("./run.js");
|
|
26
|
+
await runCommand.parseAsync(["node", "summer", fullPath], { from: "user" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`Opening project: ${fullPath}`);
|
|
30
|
+
console.log("Note: To switch projects, close the current one in Summer Engine first,\n" +
|
|
31
|
+
"or run: summer run " + fullPath);
|
|
32
|
+
});
|