summer-engine 0.0.1 → 1.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.
Files changed (53) hide show
  1. package/README.md +72 -8
  2. package/dist/bin/postinstall.d.ts +2 -0
  3. package/dist/bin/postinstall.js +3 -0
  4. package/dist/bin/summer.d.ts +2 -0
  5. package/dist/bin/summer.js +34 -0
  6. package/dist/commands/create.d.ts +2 -0
  7. package/dist/commands/create.js +99 -0
  8. package/dist/commands/install.d.ts +2 -0
  9. package/dist/commands/install.js +135 -0
  10. package/dist/commands/list.d.ts +2 -0
  11. package/dist/commands/list.js +48 -0
  12. package/dist/commands/login.d.ts +2 -0
  13. package/dist/commands/login.js +75 -0
  14. package/dist/commands/logout.d.ts +2 -0
  15. package/dist/commands/logout.js +26 -0
  16. package/dist/commands/mcp.d.ts +2 -0
  17. package/dist/commands/mcp.js +7 -0
  18. package/dist/commands/open.d.ts +2 -0
  19. package/dist/commands/open.js +32 -0
  20. package/dist/commands/run.d.ts +2 -0
  21. package/dist/commands/run.js +69 -0
  22. package/dist/commands/skills.d.ts +2 -0
  23. package/dist/commands/skills.js +187 -0
  24. package/dist/commands/status.d.ts +2 -0
  25. package/dist/commands/status.js +43 -0
  26. package/dist/lib/api-client.d.ts +17 -0
  27. package/dist/lib/api-client.js +69 -0
  28. package/dist/lib/auth.d.ts +13 -0
  29. package/dist/lib/auth.js +39 -0
  30. package/dist/lib/banner.d.ts +4 -0
  31. package/dist/lib/banner.js +122 -0
  32. package/dist/lib/engine.d.ts +12 -0
  33. package/dist/lib/engine.js +36 -0
  34. package/dist/mcp/server.d.ts +4 -0
  35. package/dist/mcp/server.js +40 -0
  36. package/dist/mcp/tools/asset-tools.d.ts +2 -0
  37. package/dist/mcp/tools/asset-tools.js +247 -0
  38. package/dist/mcp/tools/debug-tools.d.ts +2 -0
  39. package/dist/mcp/tools/debug-tools.js +49 -0
  40. package/dist/mcp/tools/project-tools.d.ts +2 -0
  41. package/dist/mcp/tools/project-tools.js +55 -0
  42. package/dist/mcp/tools/scene-tools.d.ts +2 -0
  43. package/dist/mcp/tools/scene-tools.js +139 -0
  44. package/dist/mcp/tools/with-engine.d.ts +10 -0
  45. package/dist/mcp/tools/with-engine.js +65 -0
  46. package/package.json +22 -5
  47. package/skills/3d-lighting/SKILL.md +103 -0
  48. package/skills/fps-controller/SKILL.md +131 -0
  49. package/skills/gdscript-patterns/SKILL.md +96 -0
  50. package/skills/gdscript-patterns/reference.md +55 -0
  51. package/skills/scene-composition/SKILL.md +108 -0
  52. package/skills/ui-basics/SKILL.md +121 -0
  53. package/bin/summer.js +0 -3
@@ -0,0 +1,69 @@
1
+ import { Command } from "commander";
2
+ import { spawn } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { platform } from "os";
5
+ import { getApiPort, checkEngineHealth } from "../lib/engine.js";
6
+ const MAC_PATHS = [
7
+ "/Applications/Summer.app/Contents/MacOS/Summer",
8
+ `${process.env.HOME}/Applications/Summer.app/Contents/MacOS/Summer`,
9
+ ];
10
+ const WIN_PATHS = [
11
+ `${process.env.LOCALAPPDATA}\\Programs\\Summer Engine\\Summer.exe`,
12
+ `${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
13
+ ];
14
+ function findEngineBinary() {
15
+ const paths = platform() === "darwin" ? MAC_PATHS : WIN_PATHS;
16
+ for (const p of paths) {
17
+ if (existsSync(p))
18
+ return p;
19
+ }
20
+ return null;
21
+ }
22
+ export const runCommand = new Command("run")
23
+ .description("Launch Summer Engine, optionally opening a project")
24
+ .argument("[path]", "Path to a project directory (must contain project.godot)")
25
+ .action(async (projectPath) => {
26
+ const port = await getApiPort();
27
+ const health = await checkEngineHealth(port);
28
+ if (health && !projectPath) {
29
+ console.log(`Summer Engine is already running (v${health.version}) on port ${port}`);
30
+ if (health.project_name) {
31
+ console.log(` Project: ${health.project_name}`);
32
+ }
33
+ return;
34
+ }
35
+ const binary = findEngineBinary();
36
+ if (!binary) {
37
+ console.error("Summer Engine not found. Install it first:\n" +
38
+ " summer install\n" +
39
+ " or download from https://summerengine.com/download");
40
+ process.exit(1);
41
+ }
42
+ const args = ["--editor"];
43
+ if (projectPath) {
44
+ args.unshift("--path", projectPath);
45
+ }
46
+ console.log("Launching Summer Engine...");
47
+ const child = spawn(binary, args, { detached: true, stdio: "ignore" });
48
+ child.unref();
49
+ // Wait for engine to start responding
50
+ const startTime = Date.now();
51
+ const timeout = 20000;
52
+ while (Date.now() - startTime < timeout) {
53
+ await sleep(500);
54
+ const newPort = await getApiPort();
55
+ const h = await checkEngineHealth(newPort);
56
+ if (h) {
57
+ console.log(`Summer Engine running (v${h.version}) on port ${newPort}`);
58
+ if (h.project_name) {
59
+ console.log(` Project: ${h.project_name}`);
60
+ }
61
+ return;
62
+ }
63
+ }
64
+ console.log("Summer Engine launched but API not responding yet.\n" +
65
+ "It may still be loading. Run 'summer status' to check.");
66
+ });
67
+ function sleep(ms) {
68
+ return new Promise((resolve) => setTimeout(resolve, ms));
69
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const skillsCommand: Command;
@@ -0,0 +1,187 @@
1
+ import { Command } from "commander";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, cpSync, } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { homedir } from "os";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ // Resolve skills dir: from dist/commands/skills.js -> ../../skills
8
+ const skillsDir = join(__dirname, "..", "..", "skills");
9
+ function getBuiltinSkills() {
10
+ if (!existsSync(skillsDir))
11
+ return [];
12
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
13
+ const skills = [];
14
+ for (const entry of entries) {
15
+ if (!entry.isDirectory())
16
+ continue;
17
+ const skillPath = join(skillsDir, entry.name, "SKILL.md");
18
+ if (!existsSync(skillPath))
19
+ continue;
20
+ const meta = parseSkillFrontmatter(skillPath);
21
+ if (meta)
22
+ skills.push(meta);
23
+ }
24
+ return skills;
25
+ }
26
+ function parseSkillFrontmatter(skillPath) {
27
+ try {
28
+ const content = readFileSync(skillPath, "utf-8");
29
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
30
+ if (!match)
31
+ return null;
32
+ const front = match[1];
33
+ const name = front.match(/^name:\s*(.+)$/m)?.[1]?.trim();
34
+ const description = front.match(/^description:\s*(.+)$/m)?.[1]?.trim();
35
+ if (!name || !description)
36
+ return null;
37
+ return { name, description };
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function getSkillPath(name) {
44
+ const path = join(skillsDir, name);
45
+ if (!existsSync(path) || !existsSync(join(path, "SKILL.md")))
46
+ return null;
47
+ return path;
48
+ }
49
+ export const skillsCommand = new Command("skills")
50
+ .description("Install and manage best-practice guides for AI agents building games")
51
+ .action(() => {
52
+ skillsCommand.outputHelp();
53
+ });
54
+ skillsCommand
55
+ .command("list")
56
+ .description("List available skills")
57
+ .action(() => {
58
+ const skills = getBuiltinSkills();
59
+ if (skills.length === 0) {
60
+ console.log("No skills found.");
61
+ return;
62
+ }
63
+ console.log("Available skills:\n");
64
+ for (const s of skills) {
65
+ console.log(` ${s.name.padEnd(20)} ${s.description}`);
66
+ }
67
+ console.log("\nInstall: summer skills install <name>");
68
+ });
69
+ function resolveInstallDir(target) {
70
+ if (process.env.SUMMER_SKILLS_DIR) {
71
+ return process.env.SUMMER_SKILLS_DIR;
72
+ }
73
+ if (target === "claude") {
74
+ return join(homedir(), ".claude", "skills");
75
+ }
76
+ if (target === "cursor") {
77
+ return join(homedir(), ".cursor", "skills");
78
+ }
79
+ return join(homedir(), ".summer", "skills");
80
+ }
81
+ function getTargetFromOpts(opts) {
82
+ if (opts.asClaudeSkill)
83
+ return "claude";
84
+ if (opts.asCursorSkill)
85
+ return "cursor";
86
+ return "summer";
87
+ }
88
+ skillsCommand
89
+ .command("install [name]")
90
+ .description("Install a skill to ~/.summer/skills/ (or use --as-claude-skill / --as-cursor-skill)")
91
+ .option("--all", "Install all available skills")
92
+ .option("--as-claude-skill", "Install to ~/.claude/skills/ for Claude Code auto-discovery")
93
+ .option("--as-cursor-skill", "Install to ~/.cursor/skills/ for Cursor auto-discovery")
94
+ .action((name, opts) => {
95
+ const { all } = opts;
96
+ const target = getTargetFromOpts(opts);
97
+ const targetDir = resolveInstallDir(target);
98
+ if (all) {
99
+ const skills = getBuiltinSkills();
100
+ if (skills.length === 0) {
101
+ console.log("No skills found.");
102
+ return;
103
+ }
104
+ if (!existsSync(targetDir)) {
105
+ mkdirSync(targetDir, { recursive: true });
106
+ }
107
+ for (const s of skills) {
108
+ const src = getSkillPath(s.name);
109
+ if (src) {
110
+ const dest = join(targetDir, s.name);
111
+ cpSync(src, dest, { recursive: true });
112
+ console.log(` Installed ${s.name}`);
113
+ }
114
+ }
115
+ console.log(`\nInstalled ${skills.length} skills to ${targetDir}`);
116
+ if (target === "claude") {
117
+ console.log("Claude Code will auto-discover these skills.");
118
+ }
119
+ else if (target === "cursor") {
120
+ console.log("Cursor will auto-discover these skills.");
121
+ }
122
+ else {
123
+ console.log("AI agents can read: <dir>/<name>/SKILL.md");
124
+ }
125
+ return;
126
+ }
127
+ if (!name) {
128
+ console.error("Specify a skill name or use --all to install all skills.");
129
+ console.log("\nAvailable skills:");
130
+ for (const s of getBuiltinSkills()) {
131
+ console.log(` ${s.name}`);
132
+ }
133
+ process.exit(1);
134
+ }
135
+ const src = getSkillPath(name);
136
+ if (!src) {
137
+ console.error(`Unknown skill: ${name}`);
138
+ const skills = getBuiltinSkills();
139
+ if (skills.length > 0) {
140
+ console.log("\nAvailable skills:");
141
+ for (const s of skills) {
142
+ console.log(` ${s.name}`);
143
+ }
144
+ }
145
+ process.exit(1);
146
+ }
147
+ if (!existsSync(targetDir)) {
148
+ mkdirSync(targetDir, { recursive: true });
149
+ }
150
+ const dest = join(targetDir, name);
151
+ cpSync(src, dest, { recursive: true });
152
+ console.log(`Installed ${name} to ${dest}`);
153
+ if (target === "claude") {
154
+ console.log("Claude Code will auto-discover this skill.");
155
+ }
156
+ else if (target === "cursor") {
157
+ console.log("Cursor will auto-discover this skill.");
158
+ }
159
+ else {
160
+ console.log(`\nAI agents can read: ${dest}/SKILL.md`);
161
+ }
162
+ });
163
+ skillsCommand
164
+ .command("info <name>")
165
+ .description("Show skill description and preview")
166
+ .action((name) => {
167
+ const src = getSkillPath(name);
168
+ if (!src) {
169
+ console.error(`Unknown skill: ${name}`);
170
+ process.exit(1);
171
+ }
172
+ const skillPath = join(src, "SKILL.md");
173
+ const content = readFileSync(skillPath, "utf-8");
174
+ const meta = parseSkillFrontmatter(skillPath);
175
+ if (meta) {
176
+ console.log(`\n${meta.name}`);
177
+ console.log("─".repeat(40));
178
+ console.log(meta.description);
179
+ console.log("\n" + "─".repeat(40));
180
+ }
181
+ const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
182
+ const preview = body.split("\n").slice(0, 30).join("\n");
183
+ console.log(preview);
184
+ if (body.split("\n").length > 30) {
185
+ console.log("\n...");
186
+ }
187
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const statusCommand: Command;
@@ -0,0 +1,43 @@
1
+ import { Command } from "commander";
2
+ import { getAuthToken, getUserInfo } from "../lib/auth.js";
3
+ import { getApiToken, getApiPort, checkEngineHealth } from "../lib/engine.js";
4
+ export const statusCommand = new Command("status")
5
+ .description("Check Summer Engine status, connection, and auth state")
6
+ .action(async () => {
7
+ console.log("Summer Engine Status\n");
8
+ const authToken = await getAuthToken();
9
+ const userInfo = await getUserInfo();
10
+ if (authToken) {
11
+ console.log(` Auth: Logged in${userInfo ? ` as ${userInfo.email}` : ""}`);
12
+ }
13
+ else {
14
+ console.log(" Auth: Not logged in");
15
+ console.log(" Run: npx summer-engine login");
16
+ console.log(" Or: https://www.summerengine.com/login");
17
+ }
18
+ const apiToken = await getApiToken();
19
+ const port = await getApiPort();
20
+ if (!apiToken) {
21
+ console.log(" Engine: Not running (no api-token found)");
22
+ console.log("\n To start: summer run");
23
+ return;
24
+ }
25
+ console.log(` API Token: Found (~/.summer/api-token)`);
26
+ console.log(` Port: ${port}`);
27
+ const health = await checkEngineHealth(port);
28
+ if (!health) {
29
+ console.log(" Engine: Not responding (may have closed since last launch)");
30
+ console.log("\n To start: summer run");
31
+ return;
32
+ }
33
+ console.log(` Engine: Running (v${health.version})`);
34
+ if (health.project_name) {
35
+ console.log(` Project: ${health.project_name}`);
36
+ }
37
+ if (health.project_path) {
38
+ console.log(` Path: ${health.project_path}`);
39
+ }
40
+ if (health.scene) {
41
+ console.log(` Scene: ${health.scene}`);
42
+ }
43
+ });
@@ -0,0 +1,17 @@
1
+ export declare class EngineApiClient {
2
+ private port;
3
+ private token;
4
+ constructor(port: number, token: string);
5
+ static connect(): Promise<EngineApiClient>;
6
+ private request;
7
+ health(): Promise<unknown>;
8
+ executeOps(ops: Record<string, unknown>[], options?: Record<string, unknown>): Promise<unknown>;
9
+ getSceneState(): Promise<unknown>;
10
+ getProjectState(): Promise<unknown>;
11
+ getDiagnostics(): Promise<unknown>;
12
+ inspectNode(path: string): Promise<unknown>;
13
+ inspectResource(path: string): Promise<unknown>;
14
+ getScriptErrors(path: string): Promise<unknown>;
15
+ play(scene?: string): Promise<unknown>;
16
+ stop(): Promise<unknown>;
17
+ }
@@ -0,0 +1,69 @@
1
+ import { getApiToken, getApiPort, checkEngineHealth } from "./engine.js";
2
+ export class EngineApiClient {
3
+ port;
4
+ token;
5
+ constructor(port, token) {
6
+ this.port = port;
7
+ this.token = token;
8
+ }
9
+ static async connect() {
10
+ const port = await getApiPort();
11
+ const token = await getApiToken();
12
+ if (!token) {
13
+ throw new Error("Summer Engine is not running (no api-token found). Open Summer Engine first.");
14
+ }
15
+ const health = await checkEngineHealth(port);
16
+ if (!health) {
17
+ throw new Error(`Summer Engine is not responding on port ${port}. Make sure it's open.`);
18
+ }
19
+ return new EngineApiClient(port, token);
20
+ }
21
+ async request(method, path, body) {
22
+ const url = `http://127.0.0.1:${this.port}${path}`;
23
+ const headers = {
24
+ Authorization: `Bearer ${this.token}`,
25
+ "Content-Type": "application/json",
26
+ };
27
+ const res = await fetch(url, {
28
+ method,
29
+ headers,
30
+ body: body ? JSON.stringify(body) : undefined,
31
+ signal: AbortSignal.timeout(30000),
32
+ });
33
+ if (!res.ok) {
34
+ const text = await res.text().catch(() => "");
35
+ throw new Error(`Engine API error ${res.status}: ${text.slice(0, 200)}`);
36
+ }
37
+ return res.json();
38
+ }
39
+ async health() {
40
+ return this.request("GET", "/api/health");
41
+ }
42
+ async executeOps(ops, options) {
43
+ return this.request("POST", "/api/ops", { ops, options });
44
+ }
45
+ async getSceneState() {
46
+ return this.request("GET", "/api/state/scene");
47
+ }
48
+ async getProjectState() {
49
+ return this.request("GET", "/api/state/project");
50
+ }
51
+ async getDiagnostics() {
52
+ return this.request("GET", "/api/state/diagnostics");
53
+ }
54
+ async inspectNode(path) {
55
+ return this.request("GET", `/api/state/inspector?path=${encodeURIComponent(path)}`);
56
+ }
57
+ async inspectResource(path) {
58
+ return this.request("GET", `/api/state/resource?path=${encodeURIComponent(path)}`);
59
+ }
60
+ async getScriptErrors(path) {
61
+ return this.request("GET", `/api/state/script-errors?path=${encodeURIComponent(path)}`);
62
+ }
63
+ async play(scene) {
64
+ return this.request("POST", "/api/play", scene ? { scene } : {});
65
+ }
66
+ async stop() {
67
+ return this.request("POST", "/api/stop");
68
+ }
69
+ }
@@ -0,0 +1,13 @@
1
+ export declare function getSummerDir(): string;
2
+ export declare function getAuthToken(): Promise<string | null>;
3
+ export declare function saveAuthToken(token: string): Promise<void>;
4
+ export declare function getUserInfo(): Promise<{
5
+ id: string;
6
+ email: string;
7
+ name?: string;
8
+ } | null>;
9
+ export declare function saveUserInfo(info: {
10
+ id: string;
11
+ email: string;
12
+ name?: string;
13
+ }): Promise<void>;
@@ -0,0 +1,39 @@
1
+ import { readFile, writeFile, mkdir } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ const SUMMER_DIR = join(homedir(), ".summer");
6
+ export function getSummerDir() {
7
+ return SUMMER_DIR;
8
+ }
9
+ async function ensureSummerDir() {
10
+ if (!existsSync(SUMMER_DIR)) {
11
+ await mkdir(SUMMER_DIR, { recursive: true, mode: 0o700 });
12
+ }
13
+ }
14
+ export async function getAuthToken() {
15
+ try {
16
+ const token = await readFile(join(SUMMER_DIR, "auth-token"), "utf-8");
17
+ return token.trim() || null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ export async function saveAuthToken(token) {
24
+ await ensureSummerDir();
25
+ await writeFile(join(SUMMER_DIR, "auth-token"), token, { encoding: "utf-8", mode: 0o600 });
26
+ }
27
+ export async function getUserInfo() {
28
+ try {
29
+ const data = await readFile(join(SUMMER_DIR, "user.json"), "utf-8");
30
+ return JSON.parse(data);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export async function saveUserInfo(info) {
37
+ await ensureSummerDir();
38
+ await writeFile(join(SUMMER_DIR, "user.json"), JSON.stringify(info, null, 2), { encoding: "utf-8", mode: 0o600 });
39
+ }
@@ -0,0 +1,4 @@
1
+ export declare function getBanner(version?: string): string;
2
+ export declare function getWelcome(version: string): string;
3
+ export declare function printBanner(version?: string): void;
4
+ export declare function printWelcome(version: string): void;
@@ -0,0 +1,122 @@
1
+ export function getBanner(version) {
2
+ const reset = "\x1b[0m";
3
+ // Vertical gradient for SUMMER ENGINE
4
+ const colors = [
5
+ "\x1b[38;2;255;223;137m", // row 1
6
+ "\x1b[38;2;255;203;107m", // row 2
7
+ "\x1b[38;2;255;183;78m", // row 3
8
+ "\x1b[38;2;255;163;49m", // row 4
9
+ "\x1b[38;2;255;143;23m", // row 5
10
+ "\x1b[38;2;255;123;0m", // row 6
11
+ "\x1b[38;2;242;104;0m", // row 7
12
+ "\x1b[38;2;229;85;0m", // row 8
13
+ "\x1b[38;2;215;67;0m", // row 9
14
+ "\x1b[38;2;201;50;0m", // row 10
15
+ "\x1b[38;2;187;34;0m", // row 11
16
+ "\x1b[38;2;173;20;0m", // row 12
17
+ ];
18
+ const headerLines = [
19
+ " ██████╗ ██╗ ██╗███╗ ███╗███╗ ███╗███████╗██████╗ ",
20
+ " ██╔════╝ ██║ ██║████╗ ████║████╗ ████║██╔════╝██╔══██╗",
21
+ " ███████╗ ██║ ██║██╔████╔██║██╔████╔██║█████╗ ██████╔╝",
22
+ " ╚════██║ ██║ ██║██║╚██╔╝██║██║╚██╔╝██║██╔══╝ ██╔══██╗",
23
+ " ███████║ ╚██████╔╝██║ ╚═╝ ██║██║ ╚═╝ ██║███████╗██║ ██║",
24
+ " ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝",
25
+ " ███████╗███╗ ██╗ ██████╗ ██╗███╗ ██╗███████╗ ",
26
+ " ██╔════╝████╗ ██║██╔════╝ ██║████╗ ██║██╔════╝ ",
27
+ " █████╗ ██╔██╗ ██║██║ ███╗██║██╔██╗ ██║█████╗ ",
28
+ " ██╔══╝ ██║╚██╗██║██║ ██║██║██║╚██╗██║██╔══╝ ",
29
+ " ███████╗██║ ╚████║╚██████╔╝██║██║ ╚████║███████╗ ",
30
+ " ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝╚══════╝ ",
31
+ ];
32
+ return headerLines.map((line, i) => `${colors[i]}${line}${reset}`).join("\n");
33
+ }
34
+ export function getWelcome(version) {
35
+ const reset = "\x1b[0m";
36
+ const dim = "\x1b[2m";
37
+ const bold = "\x1b[1m";
38
+ const sunC1 = "\x1b[38;2;255;223;137m"; // Light yellow
39
+ const sunC2 = "\x1b[38;2;255;163;49m"; // Orange
40
+ const sunC3 = "\x1b[38;2;229;85;0m"; // Red-orange
41
+ function stripAnsi(str) {
42
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
43
+ }
44
+ function pad(str, target) {
45
+ const visLen = stripAnsi(str).length;
46
+ return str + " ".repeat(Math.max(0, target - visLen));
47
+ }
48
+ const sessionId = Date.now().toString().slice(-6).padEnd(6, ' ');
49
+ // Left column (Sun) fixed 27 width
50
+ const left = [
51
+ pad("", 27),
52
+ pad(` ${sunC2}|${reset}`, 27),
53
+ pad(` ${sunC1}\\${reset} ${sunC2}|${reset} ${sunC1}/${reset}`, 27),
54
+ pad(` ${sunC1}.${reset} ${sunC2}..::.:..${reset} ${sunC1}.${reset}`, 27),
55
+ pad(` ${sunC2}.:::"""":::.${reset}`, 27),
56
+ pad(` ${sunC3}---:::${sunC2}' '${sunC3}:::---${reset}`, 27),
57
+ pad(` ${sunC3}::: :::${reset}`, 27),
58
+ pad(` ${sunC3}---:::. .:::---${reset}`, 27),
59
+ pad(` ${sunC3}':::....:::'${reset}`, 27),
60
+ pad(` ${sunC1}'${reset} ${sunC3}''::::''${reset} ${sunC1}'${reset}`, 27),
61
+ pad(` ${sunC1}/${reset} ${sunC3}|${reset} ${sunC1}\\${reset}`, 27),
62
+ pad(` ${sunC3}|${reset}`, 27),
63
+ pad("", 27),
64
+ pad("", 27),
65
+ pad("", 27)
66
+ ];
67
+ // Right column fixed 42 width
68
+ const right = [
69
+ pad("", 42),
70
+ pad(`${bold}Available Commands${reset}`, 42),
71
+ pad(`${dim}summer install:${reset} Download the engine`, 42),
72
+ pad(`${dim}summer login:${reset} Sign in to your account`, 42),
73
+ pad(`${dim}summer create:${reset} Create a new project`, 42),
74
+ pad(`${dim}summer mcp:${reset} Start MCP server`, 42),
75
+ pad("", 42),
76
+ pad(`${bold}Available Skills${reset}`, 42),
77
+ pad(`cloud: ${dim}animation, texturing${reset}`, 42),
78
+ pad(`local: ${dim}debugging, 2d, scene${reset}`, 42),
79
+ pad("", 42),
80
+ pad(`${bold}Connected Engine${reset}`, 42),
81
+ pad(`Status: ${dim}Ready${reset}`, 42),
82
+ pad(`Version: ${dim}Summer Engine v${version}${reset}`, 42),
83
+ pad(`Session: ${dim}summer_${sessionId}${reset}`, 42)
84
+ ];
85
+ const footerLines = [
86
+ pad("", 69),
87
+ pad(` ${sunC2}summer-engine-cli${reset} · ${dim}local${reset}`, 69),
88
+ pad(` ${dim}Docs: https://summerengine.com/docs/mcp${reset}`, 69)
89
+ ];
90
+ const title = ` Summer Engine CLI v${version} `;
91
+ const titleLen = stripAnsi(title).length;
92
+ const totalDashes = 69 - titleLen;
93
+ const leftDashes = Math.floor(totalDashes / 2);
94
+ const rightDashes = totalDashes - leftDashes;
95
+ const boxTop = `${dim}┌${"─".repeat(leftDashes)}${title}${"─".repeat(rightDashes)}┐${reset}`;
96
+ const boxBottom = `${dim}└${"─".repeat(69)}┘${reset}`;
97
+ const bodyLines = [
98
+ ...left.map((l, i) => `${dim}│${reset}${l}${right[i]}${dim}│${reset}`),
99
+ ...footerLines.map(f => `${dim}│${reset}${f}${dim}│${reset}`)
100
+ ];
101
+ return [
102
+ "",
103
+ getBanner(),
104
+ "",
105
+ boxTop,
106
+ ...bodyLines,
107
+ boxBottom,
108
+ "",
109
+ `⚠️ ${dim}Some tools disabled (missing engine connection):${reset}`,
110
+ `• Scene Operations ${dim}(needs running editor instance)${reset}`,
111
+ `Run '${bold}summer mcp${reset}' to connect.`,
112
+ "",
113
+ `Welcome to Summer CLI! Type your message or /help for commands.`,
114
+ ""
115
+ ].join("\n");
116
+ }
117
+ export function printBanner(version) {
118
+ console.log(getBanner(version));
119
+ }
120
+ export function printWelcome(version) {
121
+ console.log(getWelcome(version));
122
+ }
@@ -0,0 +1,12 @@
1
+ export declare function getApiToken(): Promise<string | null>;
2
+ export declare function getApiPort(): Promise<number>;
3
+ export interface EngineHealth {
4
+ ok: boolean;
5
+ engine: string;
6
+ version: string;
7
+ port: number;
8
+ project_name?: string;
9
+ project_path?: string;
10
+ scene?: string;
11
+ }
12
+ export declare function checkEngineHealth(port: number): Promise<EngineHealth | null>;
@@ -0,0 +1,36 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { getSummerDir } from "./auth.js";
4
+ const DEFAULT_PORT = 6550;
5
+ export async function getApiToken() {
6
+ try {
7
+ const token = await readFile(join(getSummerDir(), "api-token"), "utf-8");
8
+ return token.trim() || null;
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ export async function getApiPort() {
15
+ try {
16
+ const port = await readFile(join(getSummerDir(), "api-port"), "utf-8");
17
+ const parsed = parseInt(port.trim(), 10);
18
+ return isNaN(parsed) ? DEFAULT_PORT : parsed;
19
+ }
20
+ catch {
21
+ return DEFAULT_PORT;
22
+ }
23
+ }
24
+ export async function checkEngineHealth(port) {
25
+ try {
26
+ const res = await fetch(`http://127.0.0.1:${port}/api/health`, {
27
+ signal: AbortSignal.timeout(2000),
28
+ });
29
+ if (!res.ok)
30
+ return null;
31
+ return (await res.json());
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ import { EngineApiClient } from "../lib/api-client.js";
2
+ export declare function getClient(): Promise<EngineApiClient>;
3
+ export declare function resetClient(): void;
4
+ export declare function startMcpServer(): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { createRequire } from "node:module";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { EngineApiClient } from "../lib/api-client.js";
5
+ import { registerSceneTools } from "./tools/scene-tools.js";
6
+ import { registerDebugTools } from "./tools/debug-tools.js";
7
+ import { registerProjectTools } from "./tools/project-tools.js";
8
+ import { registerAssetTools } from "./tools/asset-tools.js";
9
+ const require = createRequire(import.meta.url);
10
+ const { version } = require("../../package.json");
11
+ let cachedClient = null;
12
+ export async function getClient() {
13
+ if (cachedClient) {
14
+ return cachedClient;
15
+ }
16
+ try {
17
+ cachedClient = await EngineApiClient.connect();
18
+ return cachedClient;
19
+ }
20
+ catch {
21
+ cachedClient = null;
22
+ throw new Error("Summer Engine is not running. Open it first, or run: npx summer-engine run");
23
+ }
24
+ }
25
+ export function resetClient() {
26
+ cachedClient = null;
27
+ }
28
+ export async function startMcpServer() {
29
+ const server = new McpServer({
30
+ name: "summer-engine",
31
+ version,
32
+ });
33
+ registerSceneTools(server);
34
+ registerDebugTools(server);
35
+ registerProjectTools(server);
36
+ registerAssetTools(server);
37
+ const transport = new StdioServerTransport();
38
+ await server.connect(transport);
39
+ process.stderr.write(`[summer-mcp] MCP server running v${version}. 28 tools available.\n`);
40
+ }