summer-engine 1.0.0 → 1.1.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.
@@ -11,6 +11,7 @@ import { installCommand } from "../commands/install.js";
11
11
  import { createCommand } from "../commands/create.js";
12
12
  import { listCommand } from "../commands/list.js";
13
13
  import { skillsCommand } from "../commands/skills.js";
14
+ import { orchestratorCommand } from "../commands/orchestrator.js";
14
15
  const require = createRequire(import.meta.url);
15
16
  const { version } = require("../../package.json");
16
17
  const program = new Command();
@@ -28,6 +29,7 @@ program.addCommand(createCommand);
28
29
  program.addCommand(listCommand);
29
30
  program.addCommand(skillsCommand);
30
31
  program.addCommand(mcpCommand);
32
+ program.addCommand(orchestratorCommand);
31
33
  program.parseAsync().catch((err) => {
32
34
  console.error(err instanceof Error ? err.message : String(err));
33
35
  process.exit(1);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CLI command: summer agent
3
+ * Launches the web app locally with direct engine execution enabled.
4
+ * The web app handles all AI logic (prompts, tools, RAG, billing).
5
+ * This command just starts it with LOCAL_ENGINE_PORT set.
6
+ */
7
+ import { Command } from "commander";
8
+ export declare const orchestratorCommand: Command;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * CLI command: summer agent
3
+ * Launches the web app locally with direct engine execution enabled.
4
+ * The web app handles all AI logic (prompts, tools, RAG, billing).
5
+ * This command just starts it with LOCAL_ENGINE_PORT set.
6
+ */
7
+ import { Command } from "commander";
8
+ import { spawn } from "node:child_process";
9
+ import { readFile, access, writeFile } from "node:fs/promises";
10
+ import { join, resolve, dirname } from "node:path";
11
+ import { homedir } from "node:os";
12
+ import { getApiToken, getApiPort, checkEngineHealth } from "../lib/engine.js";
13
+ import { getSummerDir } from "../lib/auth.js";
14
+ async function findWebAppPath() {
15
+ // 1. Check env var
16
+ if (process.env.SUMMER_WEB_APP_PATH) {
17
+ return process.env.SUMMER_WEB_APP_PATH;
18
+ }
19
+ // 2. Check ~/.summer/web-app-path
20
+ try {
21
+ const stored = (await readFile(join(getSummerDir(), "web-app-path"), "utf-8")).trim();
22
+ if (stored) {
23
+ await access(join(stored, "package.json"));
24
+ return stored;
25
+ }
26
+ }
27
+ catch {
28
+ // Not stored yet
29
+ }
30
+ // 3. Search common sibling locations
31
+ const cliDir = dirname(dirname(dirname(new URL(import.meta.url).pathname)));
32
+ const searchPaths = [
33
+ resolve(cliDir, "../../publicsummerengine"), // sibling in development/
34
+ resolve(cliDir, "../../../publicsummerengine"), // one level up
35
+ resolve(homedir(), "development/publicsummerengine"),
36
+ ];
37
+ for (const candidate of searchPaths) {
38
+ try {
39
+ await access(join(candidate, "package.json"));
40
+ const pkg = JSON.parse(await readFile(join(candidate, "package.json"), "utf-8"));
41
+ if (pkg.name && pkg.name.includes("summer")) {
42
+ // Store for future use
43
+ try {
44
+ await writeFile(join(getSummerDir(), "web-app-path"), candidate, "utf-8");
45
+ }
46
+ catch {
47
+ // Non-critical
48
+ }
49
+ return candidate;
50
+ }
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ async function loadEnvFile() {
59
+ const envVars = {};
60
+ try {
61
+ const envPath = join(getSummerDir(), "env");
62
+ const content = await readFile(envPath, "utf-8");
63
+ for (const line of content.split("\n")) {
64
+ const trimmed = line.trim();
65
+ if (!trimmed || trimmed.startsWith("#"))
66
+ continue;
67
+ const eqIdx = trimmed.indexOf("=");
68
+ if (eqIdx > 0) {
69
+ envVars[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
70
+ }
71
+ }
72
+ }
73
+ catch {
74
+ // No env file — user will need to have env vars set
75
+ }
76
+ return envVars;
77
+ }
78
+ export const orchestratorCommand = new Command("agent")
79
+ .description("Start the web app locally with direct engine execution. " +
80
+ "Tools call localhost:6550 instead of the Redis bridge.")
81
+ .option("-p, --port <number>", "Web app port", "3000")
82
+ .option("--web-path <path>", "Path to publicsummerengine (auto-detected if not set)")
83
+ .option("--dev", "Run in dev mode (pnpm dev) instead of production")
84
+ .action(async (opts) => {
85
+ const port = parseInt(opts.port, 10);
86
+ // 1. Verify engine is running
87
+ const enginePort = await getApiPort();
88
+ const engineToken = await getApiToken();
89
+ const health = await checkEngineHealth(enginePort);
90
+ if (!health) {
91
+ console.error("Summer Engine is not running. Open Summer Engine first.");
92
+ process.exit(1);
93
+ }
94
+ console.log(`Engine running on port ${enginePort} | project: ${health.project_name ?? "unknown"}`);
95
+ // 2. Find web app
96
+ const webAppPath = opts.webPath ?? (await findWebAppPath());
97
+ if (!webAppPath) {
98
+ console.error("Could not find publicsummerengine. Set --web-path or SUMMER_WEB_APP_PATH.");
99
+ process.exit(1);
100
+ }
101
+ console.log(`Web app: ${webAppPath}`);
102
+ // 3. Load env vars from ~/.summer/env
103
+ const storedEnv = await loadEnvFile();
104
+ // 4. Build env for the web app
105
+ const childEnv = {
106
+ ...process.env,
107
+ ...storedEnv,
108
+ LOCAL_ENGINE_PORT: String(enginePort),
109
+ LOCAL_ENGINE_TOKEN: engineToken ?? "",
110
+ PORT: String(port),
111
+ };
112
+ // 5. Start the web app
113
+ const cmd = opts.dev ? "pnpm" : "pnpm";
114
+ const args = opts.dev ? ["dev", "--port", String(port)] : ["start", "--port", String(port)];
115
+ console.log(`\nStarting web app (${opts.dev ? "dev" : "production"} mode) on port ${port}...`);
116
+ console.log(` LOCAL_ENGINE_PORT=${enginePort}`);
117
+ console.log(` Tools will execute via localhost:${enginePort} (direct, no bridge)\n`);
118
+ const child = spawn(cmd, args, {
119
+ cwd: webAppPath,
120
+ env: childEnv,
121
+ stdio: "inherit",
122
+ });
123
+ // Write agent port for engine to discover
124
+ try {
125
+ await writeFile(join(getSummerDir(), "agent-port"), String(port), "utf-8");
126
+ }
127
+ catch {
128
+ // Non-critical
129
+ }
130
+ child.on("error", (err) => {
131
+ console.error("Failed to start web app:", err.message);
132
+ process.exit(1);
133
+ });
134
+ child.on("exit", (code) => {
135
+ process.exit(code ?? 0);
136
+ });
137
+ // Graceful shutdown
138
+ const shutdown = () => {
139
+ child.kill("SIGTERM");
140
+ };
141
+ process.on("SIGINT", shutdown);
142
+ process.on("SIGTERM", shutdown);
143
+ });
@@ -8,6 +8,7 @@ const MAC_PATHS = [
8
8
  `${process.env.HOME}/Applications/Summer.app/Contents/MacOS/Summer`,
9
9
  ];
10
10
  const WIN_PATHS = [
11
+ `${process.env.LOCALAPPDATA}\\SummerEngine\\current\\Summer.exe`,
11
12
  `${process.env.LOCALAPPDATA}\\Programs\\Summer Engine\\Summer.exe`,
12
13
  `${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
13
14
  ];
@@ -14,4 +14,10 @@ export declare class EngineApiClient {
14
14
  getScriptErrors(path: string): Promise<unknown>;
15
15
  play(scene?: string): Promise<unknown>;
16
16
  stop(): Promise<unknown>;
17
+ readFile(path: string, maxBytes?: number): Promise<unknown>;
18
+ getFsTree(root?: string, limit?: number): Promise<unknown>;
19
+ getSelection(): Promise<unknown>;
20
+ viewportSnapshot(): Promise<Buffer>;
21
+ gameSnapshot(): Promise<Buffer>;
22
+ getPort(): number;
17
23
  }
@@ -66,4 +66,43 @@ export class EngineApiClient {
66
66
  async stop() {
67
67
  return this.request("POST", "/api/stop");
68
68
  }
69
+ async readFile(path, maxBytes) {
70
+ const params = new URLSearchParams({ path });
71
+ if (maxBytes)
72
+ params.set("maxBytes", String(maxBytes));
73
+ return this.request("GET", `/api/state/read-file?${params}`);
74
+ }
75
+ async getFsTree(root = "res://", limit = 2000) {
76
+ const params = new URLSearchParams({
77
+ root,
78
+ limit: String(limit),
79
+ });
80
+ return this.request("GET", `/api/state/fs-tree?${params}`);
81
+ }
82
+ async getSelection() {
83
+ return this.request("GET", "/api/state/selection");
84
+ }
85
+ async viewportSnapshot() {
86
+ const url = `http://127.0.0.1:${this.port}/api/snapshot/viewport`;
87
+ const res = await fetch(url, {
88
+ headers: { Authorization: `Bearer ${this.token}` },
89
+ signal: AbortSignal.timeout(15000),
90
+ });
91
+ if (!res.ok)
92
+ throw new Error(`Viewport snapshot failed: ${res.status}`);
93
+ return Buffer.from(await res.arrayBuffer());
94
+ }
95
+ async gameSnapshot() {
96
+ const url = `http://127.0.0.1:${this.port}/api/snapshot/game`;
97
+ const res = await fetch(url, {
98
+ headers: { Authorization: `Bearer ${this.token}` },
99
+ signal: AbortSignal.timeout(15000),
100
+ });
101
+ if (!res.ok)
102
+ throw new Error(`Game snapshot failed: ${res.status}`);
103
+ return Buffer.from(await res.arrayBuffer());
104
+ }
105
+ getPort() {
106
+ return this.port;
107
+ }
69
108
  }
@@ -36,5 +36,5 @@ export async function startMcpServer() {
36
36
  registerAssetTools(server);
37
37
  const transport = new StdioServerTransport();
38
38
  await server.connect(transport);
39
- process.stderr.write(`[summer-mcp] MCP server running v${version}. 28 tools available.\n`);
39
+ process.stderr.write(`[summer-mcp] MCP server running v${version}.\n`);
40
40
  }
@@ -1,6 +1,93 @@
1
1
  import { z } from "zod";
2
2
  import { withEngine } from "./with-engine.js";
3
+ import { readFile } from "fs/promises";
4
+ import { join } from "path";
5
+ async function readMainSceneFromProject(projectPath) {
6
+ if (!projectPath)
7
+ return null;
8
+ try {
9
+ const text = await readFile(join(projectPath, "project.godot"), "utf-8");
10
+ const match = text.match(/run\/main_scene="([^"]+)"/);
11
+ return match?.[1] ?? null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
3
17
  export function registerProjectTools(server) {
18
+ server.tool("summer_get_agent_playbook", `AI-first operating guide for Summer Engine MCP.
19
+
20
+ Call this at the start of a fresh chat before touching scenes.
21
+ It returns the safe workflow, anti-patterns, and recovery steps.`, {}, async () => ({
22
+ content: [
23
+ {
24
+ type: "text",
25
+ text: JSON.stringify({
26
+ startupChecklist: [
27
+ "Decide first: file edit or engine operation.",
28
+ "For normal code/content edits (.gd/.tscn/.tres/.ts), use host file-edit tools directly.",
29
+ "Call summer_get_project_context first.",
30
+ "If no scene is open, call summer_open_main_scene.",
31
+ "Call summer_get_scene_tree before structural edits.",
32
+ "Call summer_save_scene after edits.",
33
+ ],
34
+ safeDefaults: [
35
+ "Never guess scene filenames (main.tscn/Main.tscn).",
36
+ "Do not route ordinary code edits through MCP when direct file tools are available.",
37
+ "Never remove multiple top-level nodes unless user explicitly requests destructive edits.",
38
+ "Never write .tscn/.scn with file write tools; use scene ops.",
39
+ ],
40
+ preferredFlow: [
41
+ "summer_get_project_context",
42
+ "summer_open_main_scene (if needed)",
43
+ "summer_get_scene_tree",
44
+ "edit via summer_add_node / summer_set_prop / summer_set_resource_property",
45
+ "summer_save_scene",
46
+ "summer_get_diagnostics",
47
+ ],
48
+ recovery: [
49
+ "If you see 'no scene open': run summer_open_main_scene.",
50
+ "If open_scene fails: re-check mainScene from summer_get_project_context.",
51
+ "If save fails: verify scene is open and game is not running.",
52
+ ],
53
+ }, null, 2),
54
+ },
55
+ ],
56
+ }));
57
+ server.tool("summer_get_project_context", `Get essential project context before editing. Returns:
58
+ - engine health/status
59
+ - project name and path
60
+ - current scene path (if available)
61
+ - main scene path from project.godot
62
+
63
+ Use this first in every fresh chat to avoid guessing scene filenames or editing the wrong scene.`, {}, async () => withEngine(async (client) => {
64
+ const [health, projectState] = await Promise.all([
65
+ client.health(),
66
+ client.getProjectState(),
67
+ ]);
68
+ const healthObj = (health ?? {});
69
+ const mainScene = await readMainSceneFromProject(typeof healthObj.project_path === "string" ? healthObj.project_path : undefined);
70
+ return {
71
+ health,
72
+ project: projectState,
73
+ mainScene,
74
+ guidance: mainScene
75
+ ? "Use `summer_open_scene` with `mainScene` if no scene is open."
76
+ : "Main scene not found in project.godot. Open a known scene path explicitly.",
77
+ };
78
+ }));
79
+ server.tool("summer_open_main_scene", `Open the project's configured main scene from project.godot.
80
+
81
+ Safer than guessing scene names like main.tscn/Main.tscn.
82
+ Call this when you get "no scene open".`, {}, async () => withEngine(async (client) => {
83
+ const health = (await client.health());
84
+ const projectPath = typeof health.project_path === "string" ? health.project_path : undefined;
85
+ const mainScene = await readMainSceneFromProject(projectPath);
86
+ if (!mainScene) {
87
+ throw new Error("Could not resolve application/run/main_scene from project.godot. Call `summer_get_project_context` and open a scene explicitly.");
88
+ }
89
+ return client.executeOps([{ op: "OpenScene", path: mainScene }]);
90
+ }));
4
91
  server.tool("summer_project_setting", `Set a project setting in project.godot. Common settings:
5
92
  - "application/config/name" — project name
6
93
  - "application/run/main_scene" — main scene path
@@ -29,7 +116,10 @@ Example: Bind jump to Space and W:
29
116
  ];
30
117
  return client.executeOps(ops);
31
118
  }));
32
- server.tool("summer_get_scene_tree", "Get the full scene tree structure of the currently open scene. Returns all nodes with their types, paths, and children. Use this to understand the scene before making changes.", {}, async () => withEngine(async (client) => client.getSceneState()));
119
+ server.tool("summer_get_scene_tree", `Get the full scene tree structure of the currently open scene.
120
+
121
+ Use this before structural edits (add/remove/replace).
122
+ If you get "no edited scene", call summer_open_main_scene first.`, {}, async () => withEngine(async (client) => client.getSceneState()));
33
123
  server.tool("summer_import_from_url", `Download a file from a URL and import it into the project. Triggers Godot's full import pipeline — generates .import files, extracts textures from .glb models, creates materials.
34
124
 
35
125
  Use this for:
@@ -1,8 +1,79 @@
1
1
  import { z } from "zod";
2
2
  import { withEngine } from "./with-engine.js";
3
+ import { readFile } from "fs/promises";
4
+ import { join } from "path";
5
+ async function readMainSceneFromProject(projectPath) {
6
+ if (!projectPath)
7
+ return null;
8
+ try {
9
+ const text = await readFile(join(projectPath, "project.godot"), "utf-8");
10
+ const match = text.match(/run\/main_scene="([^"]+)"/);
11
+ return match?.[1] ?? null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
3
17
  export function registerSceneTools(server) {
18
+ server.tool("summer_create_scene", `Create a new empty scene file safely.
19
+
20
+ IMPORTANT:
21
+ - This tool uses a temporary mutation strategy: it opens a template scene, removes children, saves to a new path, then restores the previous scene.
22
+ - To prevent accidental destructive actions, you MUST explicitly set allow_temporary_scene_mutation=true.
23
+ - If you don't want this strategy, stop and ask the user for manual scene creation in the editor.
24
+
25
+ Recommended workflow:
26
+ 1) Call summer_get_project_context
27
+ 2) Call summer_open_main_scene (optional)
28
+ 3) Call summer_create_scene with a new path`, {
29
+ path: z.string().describe("New scene path, e.g. 'res://scenes/empty_level.tscn'"),
30
+ rootName: z.string().default("Main").describe("Root node name for the new scene"),
31
+ allow_temporary_scene_mutation: z
32
+ .boolean()
33
+ .default(false)
34
+ .describe("Safety gate. Must be true to proceed."),
35
+ }, async ({ path, rootName, allow_temporary_scene_mutation }) => withEngine(async (client) => {
36
+ if (!allow_temporary_scene_mutation) {
37
+ throw new Error("Refusing to create scene without explicit approval. Re-run with allow_temporary_scene_mutation=true.");
38
+ }
39
+ const health = (await client.health());
40
+ const currentScene = typeof health.scene === "string" && health.scene.length > 0 ? health.scene : null;
41
+ const projectPath = typeof health.project_path === "string" ? health.project_path : undefined;
42
+ const mainScene = await readMainSceneFromProject(projectPath);
43
+ const templateScene = currentScene || mainScene;
44
+ if (!templateScene) {
45
+ throw new Error("No scene open and could not resolve main scene. Call summer_get_project_context first, then open a known scene.");
46
+ }
47
+ await client.executeOps([{ op: "OpenScene", path: templateScene }]);
48
+ const tree = (await client.getSceneState());
49
+ const children = tree.data?.children ?? tree.children ?? [];
50
+ const removeOps = children
51
+ .map((c) => c.path)
52
+ .filter((p) => typeof p === "string" && p.length > 0)
53
+ .map((p) => ({ op: "RemoveNode", path: `./${p}` }));
54
+ if (removeOps.length > 0) {
55
+ await client.executeOps(removeOps, { groupUndo: true });
56
+ }
57
+ await client.executeOps([{ op: "SetProp", path: ".", key: "name", value: rootName }]);
58
+ await client.executeOps([{ op: "SaveScene", path }]);
59
+ if (currentScene && currentScene !== path) {
60
+ await client.executeOps([{ op: "OpenScene", path: currentScene }]);
61
+ }
62
+ return {
63
+ ok: true,
64
+ created: path,
65
+ rootName,
66
+ templateScene,
67
+ restoredScene: currentScene,
68
+ };
69
+ }));
4
70
  server.tool("summer_add_node", `Add a new node to the scene tree.
5
71
 
72
+ Preflight (fresh chat):
73
+ 1) summer_get_project_context
74
+ 2) summer_open_main_scene (if no scene open)
75
+ 3) summer_get_scene_tree
76
+
6
77
  Common node types:
7
78
  - 3D: Node3D, MeshInstance3D, CharacterBody3D, RigidBody3D, StaticBody3D, Camera3D, DirectionalLight3D, OmniLight3D, SpotLight3D, WorldEnvironment, CollisionShape3D, Area3D
8
79
  - 2D: Node2D, Sprite2D, CharacterBody2D, RigidBody2D, StaticBody2D, Camera2D, CollisionShape2D, Area2D, TileMapLayer
@@ -52,14 +123,19 @@ Use when you need to modify a sub-property of a resource, like:
52
123
  }, async ({ nodePath, resourceProperty, subProperty, value }) => withEngine(async (client) => client.executeOps([
53
124
  { op: "SetResourceProperty", nodePath, resourceProperty, subProperty, value },
54
125
  ])));
55
- server.tool("summer_remove_node", "Remove a node from the scene tree. All children are removed too. Cannot remove the root node. Supports undo.", { path: z.string().describe("Node path to remove, e.g. './World/OldEnemy'") }, async ({ path }) => withEngine(async (client) => client.executeOps([{ op: "RemoveNode", path }])));
126
+ server.tool("summer_remove_node", "Remove a node from the scene tree. All children are removed too. Cannot remove the root node. Supports undo. Destructive operation: do not delete multiple top-level nodes unless the user explicitly requests destructive changes.", { path: z.string().describe("Node path to remove, e.g. './World/OldEnemy'") }, async ({ path }) => withEngine(async (client) => client.executeOps([{ op: "RemoveNode", path }])));
56
127
  server.tool("summer_save_scene", "Save the current scene to disk. Call this after making scene changes to persist them. Without saving, changes only exist in the editor's memory.", { path: z.string().optional().describe("Save-as path for creating a new scene file, e.g. 'res://levels/level2.tscn'") }, async ({ path }) => withEngine(async (client) => {
57
128
  const op = { op: "SaveScene" };
58
129
  if (path)
59
130
  op.path = path;
60
131
  return client.executeOps([op]);
61
132
  }));
62
- server.tool("summer_open_scene", "Open a scene file in the editor. Use this to switch between scenes (e.g., open a level to edit it).", { path: z.string().describe("Scene path, e.g. 'res://main.tscn' or 'res://levels/level1.tscn'") }, async ({ path }) => withEngine(async (client) => client.executeOps([{ op: "OpenScene", path }])));
133
+ server.tool("summer_open_scene", `Open a scene file in the editor. Use this to switch between scenes.
134
+
135
+ Do not guess paths. Prefer:
136
+ 1) summer_get_project_context (read mainScene)
137
+ 2) summer_open_main_scene (open known main scene)
138
+ 3) summer_open_scene only when user gave an explicit path.`, { path: z.string().describe("Scene path, e.g. 'res://main.tscn' or 'res://levels/level1.tscn'") }, async ({ path }) => withEngine(async (client) => client.executeOps([{ op: "OpenScene", path }])));
63
139
  server.tool("summer_instantiate_scene", `Add an existing scene or 3D model as a child node. Use this to:
64
140
  - Add a .tscn prefab (reusable scene) as a child
65
141
  - Add a .glb/.gltf 3D model into the scene
@@ -1,6 +1,28 @@
1
1
  import { getClient, resetClient } from "../server.js";
2
2
  import { getAuthToken } from "../../lib/auth.js";
3
3
  const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
4
+ function extractOpError(result) {
5
+ if (!result || typeof result !== "object")
6
+ return null;
7
+ const op = result;
8
+ if (op.status === "error" && op.error)
9
+ return op.error;
10
+ const firstFailed = op.results?.find((r) => r.ok === false && r.error);
11
+ return firstFailed?.error ?? null;
12
+ }
13
+ function buildActionHint(message) {
14
+ const normalized = message.toLowerCase();
15
+ if (normalized.includes("no scene open") || normalized.includes("no edited scene")) {
16
+ return "No scene is currently open. Call `summer_get_project_context` first, then `summer_open_main_scene` (or `summer_open_scene` with a known .tscn path).";
17
+ }
18
+ if (normalized.includes("failed to open scene")) {
19
+ return "Scene path could not be opened. Call `summer_get_project_context` to get `mainScene`, then open that exact path. Avoid guessing scene filenames.";
20
+ }
21
+ if (normalized.includes("writefile cannot edit .tscn/.scn")) {
22
+ return "This MCP operation cannot write .tscn/.scn directly. If your host AI has normal file-edit tools, edit .gd/.tscn files there; use Summer MCP for engine/state/scene-tree operations.";
23
+ }
24
+ return null;
25
+ }
4
26
  async function checkMcpQuota() {
5
27
  const token = await getAuthToken();
6
28
  if (!token) {
@@ -55,6 +77,12 @@ export async function withEngine(fn) {
55
77
  }
56
78
  const client = await getClient();
57
79
  const result = await fn(client);
80
+ const opError = extractOpError(result);
81
+ if (opError) {
82
+ const hint = buildActionHint(opError);
83
+ const message = hint ? `${opError}\n\nHint: ${hint}` : opError;
84
+ return { content: [{ type: "text", text: message }], isError: true };
85
+ }
58
86
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
59
87
  }
60
88
  catch (err) {
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI and MCP tools for Summer Engine — the AI-native game engine",
5
- "keywords": ["game-engine", "godot", "ai", "mcp", "gamedev", "3d", "summer-engine"],
5
+ "keywords": [
6
+ "game-engine",
7
+ "godot",
8
+ "ai",
9
+ "mcp",
10
+ "gamedev",
11
+ "3d",
12
+ "summer-engine"
13
+ ],
6
14
  "homepage": "https://summerengine.com",
7
15
  "repository": {
8
16
  "type": "git",
@@ -14,7 +22,11 @@
14
22
  "bin": {
15
23
  "summer": "./dist/bin/summer.js"
16
24
  },
17
- "files": ["dist/", "skills/", "README.md"],
25
+ "files": [
26
+ "dist/",
27
+ "skills/",
28
+ "README.md"
29
+ ],
18
30
  "scripts": {
19
31
  "build": "tsc",
20
32
  "dev": "tsc --watch",
@@ -25,12 +37,16 @@
25
37
  "node": ">=18"
26
38
  },
27
39
  "dependencies": {
40
+ "@ai-sdk/anthropic": "^3.0.0",
28
41
  "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "ai": "^6.0.0",
29
43
  "commander": "^12.0.0",
44
+ "diff": "^7.0.0",
30
45
  "open": "^10.0.0",
31
46
  "zod": "^3.23.0"
32
47
  },
33
48
  "devDependencies": {
49
+ "@types/diff": "^7.0.0",
34
50
  "@types/node": "^20.0.0",
35
51
  "typescript": "^5.5.0"
36
52
  }