summer-engine 1.0.1 → 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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "CLI and MCP tools for Summer Engine — the AI-native game engine",
5
5
  "keywords": [
6
6
  "game-engine",
@@ -37,12 +37,16 @@
37
37
  "node": ">=18"
38
38
  },
39
39
  "dependencies": {
40
+ "@ai-sdk/anthropic": "^3.0.0",
40
41
  "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "ai": "^6.0.0",
41
43
  "commander": "^12.0.0",
44
+ "diff": "^7.0.0",
42
45
  "open": "^10.0.0",
43
46
  "zod": "^3.23.0"
44
47
  },
45
48
  "devDependencies": {
49
+ "@types/diff": "^7.0.0",
46
50
  "@types/node": "^20.0.0",
47
51
  "typescript": "^5.5.0"
48
52
  }