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.
- package/dist/bin/summer.js +2 -0
- package/dist/commands/orchestrator.d.ts +8 -0
- package/dist/commands/orchestrator.js +143 -0
- package/dist/commands/run.js +1 -0
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +39 -0
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools/asset-tools.js +14 -5
- package/dist/mcp/tools/generate-tools.d.ts +2 -0
- package/dist/mcp/tools/generate-tools.js +422 -0
- package/package.json +5 -1
- package/skills/asset-strategy/SKILL.md +235 -0
- package/skills/make-game/SKILL.md +441 -0
package/dist/bin/summer.js
CHANGED
|
@@ -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
|
+
});
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
];
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -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
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
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
|
|
94
|
-
query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon',
|
|
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
|
-
|
|
98
|
-
|
|
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: [
|