summer-engine 1.0.1 → 1.2.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
  }
@@ -6,6 +6,7 @@ import { registerSceneTools } from "./tools/scene-tools.js";
6
6
  import { registerDebugTools } from "./tools/debug-tools.js";
7
7
  import { registerProjectTools } from "./tools/project-tools.js";
8
8
  import { registerAssetTools } from "./tools/asset-tools.js";
9
+ import { registerGenerateTools } from "./tools/generate-tools.js";
9
10
  const require = createRequire(import.meta.url);
10
11
  const { version } = require("../../package.json");
11
12
  let cachedClient = null;
@@ -34,6 +35,7 @@ export async function startMcpServer() {
34
35
  registerDebugTools(server);
35
36
  registerProjectTools(server);
36
37
  registerAssetTools(server);
38
+ registerGenerateTools(server);
37
39
  const transport = new StdioServerTransport();
38
40
  await server.connect(transport);
39
41
  process.stderr.write(`[summer-mcp] MCP server running v${version}.\n`);
@@ -56,6 +56,9 @@ async function searchAssetsApi(params) {
56
56
  if (params.limit) {
57
57
  searchParams.set("limit", String(params.limit));
58
58
  }
59
+ if (params.source) {
60
+ searchParams.set("source", params.source);
61
+ }
59
62
  const res = await fetch(`${GATEWAY_URL}/api/mcp/assets?${searchParams}`, {
60
63
  headers: { Authorization: `Bearer ${token}` },
61
64
  signal: AbortSignal.timeout(15000),
@@ -85,17 +88,23 @@ async function searchAssetsApi(params) {
85
88
  return data;
86
89
  }
87
90
  export function registerAssetTools(server) {
88
- server.tool("summer_search_assets", `Search the Summer Engine asset library (25k+ game assets) by description.
91
+ server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem.
92
+
93
+ Sources:
94
+ - "library" (default) — Public asset library (25k+ community assets). Requires Pro plan.
95
+ - "my_assets" — Your own generated/uploaded assets. Free for all users. Query is optional.
96
+ - "all" — Search both library and your assets.
89
97
 
90
98
  Uses hybrid search: keywords + semantic similarity. Finds assets by name AND by meaning.
91
99
  Returns asset names, types, preview URLs, and import-ready file URLs.
92
100
 
93
- Requires authentication. If the user gets an auth error, they need to run 'npx summer-engine login' in their terminal first.`, {
94
- query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon', 'wooden crate'"),
101
+ Requires authentication: run 'npx summer-engine login' first.`, {
102
+ query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon'. For my_assets, can be empty to list recent."),
95
103
  assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("all").describe("Filter by asset type"),
96
104
  limit: z.number().default(10).describe("Max results (1-20)"),
97
- }, async ({ query, assetType, limit }) => {
98
- const result = await searchAssetsApi({ query, assetType, limit: Math.min(limit, 20) });
105
+ source: z.enum(["library", "my_assets", "all"]).default("library").describe("Where to search: library (public 25k+), my_assets (your generated assets), all"),
106
+ }, async ({ query, assetType, limit, source }) => {
107
+ const result = await searchAssetsApi({ query, assetType, limit: Math.min(limit, 20), source });
99
108
  if (result.error) {
100
109
  return {
101
110
  content: [
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGenerateTools(server: McpServer): void;