summer-engine 1.2.0 → 1.2.1
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/README.md +58 -24
- package/dist/bin/summer.js +4 -0
- package/dist/commands/doctor.d.ts +26 -0
- package/dist/commands/doctor.js +248 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/doctor.test.js +29 -0
- package/dist/commands/mcp.js +49 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +128 -0
- package/dist/commands/skills.js +246 -107
- package/dist/lib/agent-config.d.ts +39 -0
- package/dist/lib/agent-config.js +284 -0
- package/dist/lib/agent-config.test.d.ts +1 -0
- package/dist/lib/agent-config.test.js +80 -0
- package/dist/lib/api-client.d.ts +19 -2
- package/dist/lib/api-client.js +129 -14
- package/dist/lib/skills-registry.d.ts +69 -0
- package/dist/lib/skills-registry.js +118 -0
- package/dist/mcp/tools/project-tools.js +100 -23
- package/dist/mcp/tools/with-engine.js +3 -53
- package/package.json +5 -2
- package/skills/gdscript-patterns/SKILL.md +2 -2
- package/skills/make-game/SKILL.md +3 -3
- package/skills/scene-composition/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Summer Engine CLI
|
|
2
2
|
|
|
3
|
-
CLI and MCP tools for [Summer Engine](https://summerengine.com) — the AI-native game engine.
|
|
3
|
+
CLI and MCP tools for [Summer Engine](https://summerengine.com) — the AI-native game engine. MIT licensed.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -14,6 +14,28 @@ Or use without installing:
|
|
|
14
14
|
npx summer-engine <command>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Quickstart (under 2 minutes)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
summer install # Download the engine
|
|
21
|
+
summer login # Sign in via browser
|
|
22
|
+
summer create 3d-basic my-game # Create a starter project
|
|
23
|
+
summer run my-game # Open it in the engine
|
|
24
|
+
summer setup claude-code --yes # Configure MCP + install recommended skills
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Replace `claude-code` with `codex`, `cursor`, or `windsurf`. Add `--scope project` to keep the configuration inside the project.
|
|
28
|
+
|
|
29
|
+
Verify:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
summer doctor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then open your agent and try the verification prompt:
|
|
36
|
+
|
|
37
|
+
> Use Summer Engine MCP. First call `summer_get_project_context` and `summer_get_agent_playbook`, then add a `DirectionalLight3D` and `Camera3D` to the main scene, save it, and check diagnostics.
|
|
38
|
+
|
|
17
39
|
## Commands
|
|
18
40
|
|
|
19
41
|
| Command | Description |
|
|
@@ -22,29 +44,25 @@ npx summer-engine <command>
|
|
|
22
44
|
| `summer login` | Sign in via browser (Google/GitHub) |
|
|
23
45
|
| `summer logout` | Clear auth tokens |
|
|
24
46
|
| `summer status` | Check engine status, port, auth state |
|
|
47
|
+
| `summer doctor` | Diagnose Node, login, engine, local API, MCP boot |
|
|
25
48
|
| `summer run [path]` | Launch engine (optionally with a project) |
|
|
26
49
|
| `summer open <path>` | Open a project in the running engine |
|
|
27
50
|
| `summer create <template> [name]` | Create new project from template |
|
|
28
|
-
| `summer list templates` |
|
|
29
|
-
| `summer list
|
|
30
|
-
| `summer skills
|
|
31
|
-
| `summer skills install <
|
|
32
|
-
| `summer mcp` | Start MCP server (used by Cursor/Claude Code) |
|
|
51
|
+
| `summer list templates \| projects` | List available templates or local projects |
|
|
52
|
+
| `summer skills list` | Show available skills |
|
|
53
|
+
| `summer skills install <name>` | Install one skill |
|
|
54
|
+
| `summer skills install --recommended --agent <agent>` | Install the recommended set |
|
|
55
|
+
| `summer mcp` | Start the MCP server (used by Codex/Cursor/Claude Code/Windsurf) |
|
|
56
|
+
| `summer mcp setup <agent>` | Write MCP config for an agent |
|
|
57
|
+
| `summer setup <agent>` | One shot: MCP config + recommended skills + doctor |
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
summer install # Download the engine
|
|
38
|
-
summer login # Sign in
|
|
39
|
-
summer create 3d-basic my-game # Create a project
|
|
40
|
-
summer run my-game # Open it in the engine
|
|
41
|
-
```
|
|
59
|
+
Supported agents: `codex`, `claude-code`, `cursor`, `windsurf`. Supported scopes: `user` (default), `project`.
|
|
42
60
|
|
|
43
61
|
## MCP Integration
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
`summer setup <agent>` writes the right config in the right place. If you need to do it by hand:
|
|
46
64
|
|
|
47
|
-
**Cursor** (`.cursor/mcp.json`):
|
|
65
|
+
**Cursor** (`.cursor/mcp.json` per project, or `~/.cursor/mcp.json` user-wide):
|
|
48
66
|
|
|
49
67
|
```json
|
|
50
68
|
{
|
|
@@ -57,7 +75,7 @@ Use Summer Engine with Cursor, Claude Code, or any MCP-compatible AI tool.
|
|
|
57
75
|
}
|
|
58
76
|
```
|
|
59
77
|
|
|
60
|
-
**Claude Code** (`~/.claude
|
|
78
|
+
**Claude Code** (`~/.claude.json` user-wide, or `.mcp.json` per project):
|
|
61
79
|
|
|
62
80
|
```json
|
|
63
81
|
{
|
|
@@ -70,18 +88,34 @@ Use Summer Engine with Cursor, Claude Code, or any MCP-compatible AI tool.
|
|
|
70
88
|
}
|
|
71
89
|
```
|
|
72
90
|
|
|
73
|
-
|
|
91
|
+
**Codex** (`~/.codex/config.toml`):
|
|
74
92
|
|
|
75
|
-
|
|
93
|
+
```toml
|
|
94
|
+
[mcp_servers.summer-engine]
|
|
95
|
+
command = "npx"
|
|
96
|
+
args = ["summer-engine", "mcp"]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The MCP server exposes focused tools for scene manipulation, inspector/resource ops, project settings, asset import, play/stop, diagnostics, and asset search/generation. Tools the host agent already has natively (file ops, git, shell, grep) are intentionally not exposed.
|
|
100
|
+
|
|
101
|
+
**Visual feedback** (viewport and game screenshots) is currently available in the Summer Engine app via the built-in Summer Agent. MCP support for screenshots is on the MVP2 roadmap, gated on host clients reliably consuming image content.
|
|
76
102
|
|
|
77
103
|
## How It Works
|
|
78
104
|
|
|
79
|
-
1. Summer Engine runs a local API server on `localhost:6550
|
|
80
|
-
2. The MCP server connects to
|
|
81
|
-
3.
|
|
82
|
-
|
|
105
|
+
1. Summer Engine runs a local API server on `localhost:6550`.
|
|
106
|
+
2. The MCP server (`npx summer-engine mcp`) connects to that local API via a per-machine auth token (`~/.summer/api-token`) and exposes engine ops as MCP tools.
|
|
107
|
+
3. Agents call the MCP tools and edit `.gd`/`.cs`/`.json`/`.md` files directly with their own file tools. Scenes (`.tscn`) flow through MCP.
|
|
108
|
+
|
|
109
|
+
The engine must be running for MCP tools that touch scenes/assets/diagnostics. If it isn't, tools return a clear error pointing at `summer run`.
|
|
110
|
+
|
|
111
|
+
## Per-Agent Docs
|
|
83
112
|
|
|
84
|
-
|
|
113
|
+
- [Agent Kit overview](docs/AGENT_KIT.md)
|
|
114
|
+
- [Claude Code](docs/CLAUDE_CODE.md)
|
|
115
|
+
- [Codex](docs/CODEX.md)
|
|
116
|
+
- [Cursor](docs/CURSOR.md)
|
|
117
|
+
- [Skills overview](docs/SKILLS.md)
|
|
118
|
+
- [Templates overview](docs/TEMPLATES.md)
|
|
85
119
|
|
|
86
120
|
## Templates
|
|
87
121
|
|
package/dist/bin/summer.js
CHANGED
|
@@ -12,6 +12,8 @@ import { createCommand } from "../commands/create.js";
|
|
|
12
12
|
import { listCommand } from "../commands/list.js";
|
|
13
13
|
import { skillsCommand } from "../commands/skills.js";
|
|
14
14
|
import { orchestratorCommand } from "../commands/orchestrator.js";
|
|
15
|
+
import { setupCommand } from "../commands/setup.js";
|
|
16
|
+
import { doctorCommand } from "../commands/doctor.js";
|
|
15
17
|
const require = createRequire(import.meta.url);
|
|
16
18
|
const { version } = require("../../package.json");
|
|
17
19
|
const program = new Command();
|
|
@@ -29,6 +31,8 @@ program.addCommand(createCommand);
|
|
|
29
31
|
program.addCommand(listCommand);
|
|
30
32
|
program.addCommand(skillsCommand);
|
|
31
33
|
program.addCommand(mcpCommand);
|
|
34
|
+
program.addCommand(setupCommand);
|
|
35
|
+
program.addCommand(doctorCommand);
|
|
32
36
|
program.addCommand(orchestratorCommand);
|
|
33
37
|
program.parseAsync().catch((err) => {
|
|
34
38
|
console.error(err instanceof Error ? err.message : String(err));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export type DoctorStatus = "ok" | "warning" | "fail";
|
|
3
|
+
export interface DoctorCheck {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
status: DoctorStatus;
|
|
7
|
+
message: string;
|
|
8
|
+
details?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface DoctorResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
checks: DoctorCheck[];
|
|
13
|
+
summary: {
|
|
14
|
+
ok: number;
|
|
15
|
+
warnings: number;
|
|
16
|
+
failures: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
interface DoctorOptions {
|
|
20
|
+
json?: boolean;
|
|
21
|
+
quiet?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare const doctorCommand: Command;
|
|
24
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorResult>;
|
|
25
|
+
export declare function printDoctorResult(result: DoctorResult): void;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { platform } from "os";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { getAuthToken, getUserInfo } from "../lib/auth.js";
|
|
7
|
+
import { checkEngineHealth, getApiPort, getApiToken } from "../lib/engine.js";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { version } = require("../../package.json");
|
|
10
|
+
const MAC_ENGINE_PATHS = [
|
|
11
|
+
"/Applications/Summer.app/Contents/MacOS/Summer",
|
|
12
|
+
`${process.env.HOME}/Applications/Summer.app/Contents/MacOS/Summer`,
|
|
13
|
+
];
|
|
14
|
+
const WIN_ENGINE_PATHS = [
|
|
15
|
+
`${process.env.LOCALAPPDATA}\\SummerEngine\\current\\Summer.exe`,
|
|
16
|
+
`${process.env.LOCALAPPDATA}\\Programs\\Summer Engine\\Summer.exe`,
|
|
17
|
+
`${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
|
|
18
|
+
];
|
|
19
|
+
export const doctorCommand = new Command("doctor")
|
|
20
|
+
.description("Run setup and connection diagnostics")
|
|
21
|
+
.option("--json", "Print diagnostics as JSON")
|
|
22
|
+
.option("--human", "Print diagnostics in a human-readable format")
|
|
23
|
+
.action(async (opts) => {
|
|
24
|
+
const result = await runDoctor({ json: opts.json || !opts.human });
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
export async function runDoctor(options = {}) {
|
|
30
|
+
const checks = [];
|
|
31
|
+
checks.push(checkNodeVersion());
|
|
32
|
+
checks.push({
|
|
33
|
+
id: "cli-version",
|
|
34
|
+
label: "Summer CLI",
|
|
35
|
+
status: "ok",
|
|
36
|
+
message: `summer-engine CLI v${version}`,
|
|
37
|
+
});
|
|
38
|
+
checks.push(await checkLogin());
|
|
39
|
+
checks.push(checkEngineInstall());
|
|
40
|
+
checks.push(await checkLocalApi());
|
|
41
|
+
checks.push(await checkMcpBoot());
|
|
42
|
+
const result = summarizeChecks(checks);
|
|
43
|
+
if (!options.quiet) {
|
|
44
|
+
if (options.json) {
|
|
45
|
+
console.log(JSON.stringify(result, null, 2));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
printDoctorResult(result);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function checkNodeVersion() {
|
|
54
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
55
|
+
if (Number.isFinite(major) && major >= 18) {
|
|
56
|
+
return {
|
|
57
|
+
id: "node-version",
|
|
58
|
+
label: "Node.js",
|
|
59
|
+
status: "ok",
|
|
60
|
+
message: `Node ${process.version}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
id: "node-version",
|
|
65
|
+
label: "Node.js",
|
|
66
|
+
status: "fail",
|
|
67
|
+
message: `Node ${process.version} found; Summer CLI requires Node 18 or newer.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function checkLogin() {
|
|
71
|
+
const token = await getAuthToken();
|
|
72
|
+
const user = await getUserInfo();
|
|
73
|
+
if (!token) {
|
|
74
|
+
return {
|
|
75
|
+
id: "login",
|
|
76
|
+
label: "Login",
|
|
77
|
+
status: "warning",
|
|
78
|
+
message: "Not logged in. Run: summer login",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
id: "login",
|
|
83
|
+
label: "Login",
|
|
84
|
+
status: "ok",
|
|
85
|
+
message: user ? `Logged in as ${user.email}` : "Auth token found.",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function checkEngineInstall() {
|
|
89
|
+
const binary = findEngineBinary();
|
|
90
|
+
if (binary) {
|
|
91
|
+
return {
|
|
92
|
+
id: "engine-install",
|
|
93
|
+
label: "Engine Install",
|
|
94
|
+
status: "ok",
|
|
95
|
+
message: `Summer Engine binary found at ${binary}`,
|
|
96
|
+
details: { path: binary },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
id: "engine-install",
|
|
101
|
+
label: "Engine Install",
|
|
102
|
+
status: "warning",
|
|
103
|
+
message: "Summer Engine binary was not found in the default install locations.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function checkLocalApi() {
|
|
107
|
+
const token = await getApiToken();
|
|
108
|
+
const port = await getApiPort();
|
|
109
|
+
if (!token) {
|
|
110
|
+
return {
|
|
111
|
+
id: "local-api",
|
|
112
|
+
label: "Local API",
|
|
113
|
+
status: "warning",
|
|
114
|
+
message: "Engine API token not found; Summer Engine is probably not running.",
|
|
115
|
+
details: { port },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const health = await checkEngineHealth(port);
|
|
119
|
+
if (!health) {
|
|
120
|
+
return {
|
|
121
|
+
id: "local-api",
|
|
122
|
+
label: "Local API",
|
|
123
|
+
status: "warning",
|
|
124
|
+
message: `API token found, but Summer Engine is not responding on port ${port}.`,
|
|
125
|
+
details: { port },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
id: "local-api",
|
|
130
|
+
label: "Local API",
|
|
131
|
+
status: "ok",
|
|
132
|
+
message: `Summer Engine API is responding on port ${port}.`,
|
|
133
|
+
details: {
|
|
134
|
+
port,
|
|
135
|
+
version: health.version,
|
|
136
|
+
projectName: health.project_name,
|
|
137
|
+
projectPath: health.project_path,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function checkMcpBoot() {
|
|
142
|
+
const cliPath = process.argv[1];
|
|
143
|
+
if (!cliPath) {
|
|
144
|
+
return {
|
|
145
|
+
id: "mcp-boot",
|
|
146
|
+
label: "MCP Server",
|
|
147
|
+
status: "fail",
|
|
148
|
+
message: "Could not resolve the current Summer CLI entrypoint.",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const command = cliPath.endsWith(".js") ? process.execPath : cliPath;
|
|
153
|
+
const args = cliPath.endsWith(".js") ? [cliPath, "mcp"] : ["mcp"];
|
|
154
|
+
const child = spawn(command, args, {
|
|
155
|
+
env: process.env,
|
|
156
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
157
|
+
});
|
|
158
|
+
let stderr = "";
|
|
159
|
+
let stdout = "";
|
|
160
|
+
let settled = false;
|
|
161
|
+
const finish = (check) => {
|
|
162
|
+
if (settled)
|
|
163
|
+
return;
|
|
164
|
+
settled = true;
|
|
165
|
+
child.kill();
|
|
166
|
+
resolve(check);
|
|
167
|
+
};
|
|
168
|
+
const timer = setTimeout(() => {
|
|
169
|
+
finish({
|
|
170
|
+
id: "mcp-boot",
|
|
171
|
+
label: "MCP Server",
|
|
172
|
+
status: "fail",
|
|
173
|
+
message: "MCP server did not finish startup within 3 seconds.",
|
|
174
|
+
details: trimOutput({ stdout, stderr }),
|
|
175
|
+
});
|
|
176
|
+
}, 3000);
|
|
177
|
+
child.stderr.on("data", (chunk) => {
|
|
178
|
+
stderr += chunk.toString("utf-8");
|
|
179
|
+
if (stderr.includes("MCP server running")) {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
finish({
|
|
182
|
+
id: "mcp-boot",
|
|
183
|
+
label: "MCP Server",
|
|
184
|
+
status: "ok",
|
|
185
|
+
message: "MCP stdio server starts successfully.",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
child.stdout.on("data", (chunk) => {
|
|
190
|
+
stdout += chunk.toString("utf-8");
|
|
191
|
+
});
|
|
192
|
+
child.on("error", (error) => {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
finish({
|
|
195
|
+
id: "mcp-boot",
|
|
196
|
+
label: "MCP Server",
|
|
197
|
+
status: "fail",
|
|
198
|
+
message: `Could not start MCP server: ${error.message}`,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
child.on("exit", (code) => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
if (!settled) {
|
|
204
|
+
finish({
|
|
205
|
+
id: "mcp-boot",
|
|
206
|
+
label: "MCP Server",
|
|
207
|
+
status: "fail",
|
|
208
|
+
message: `MCP server exited before reporting readiness${code === null ? "" : ` (code ${code})`}.`,
|
|
209
|
+
details: trimOutput({ stdout, stderr }),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function summarizeChecks(checks) {
|
|
216
|
+
const summary = {
|
|
217
|
+
ok: checks.filter((check) => check.status === "ok").length,
|
|
218
|
+
warnings: checks.filter((check) => check.status === "warning").length,
|
|
219
|
+
failures: checks.filter((check) => check.status === "fail").length,
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
ok: summary.failures === 0,
|
|
223
|
+
checks,
|
|
224
|
+
summary,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function printDoctorResult(result) {
|
|
228
|
+
console.log("Summer Doctor\n");
|
|
229
|
+
for (const check of result.checks) {
|
|
230
|
+
const mark = check.status === "ok" ? "OK" : check.status === "warning" ? "WARN" : "FAIL";
|
|
231
|
+
console.log(` ${mark.padEnd(4)} ${check.label}: ${check.message}`);
|
|
232
|
+
}
|
|
233
|
+
console.log(`\n${result.summary.ok} ok, ${result.summary.warnings} warnings, ${result.summary.failures} failures`);
|
|
234
|
+
}
|
|
235
|
+
function findEngineBinary() {
|
|
236
|
+
const paths = platform() === "darwin" ? MAC_ENGINE_PATHS : WIN_ENGINE_PATHS;
|
|
237
|
+
for (const path of paths) {
|
|
238
|
+
if (path && existsSync(path))
|
|
239
|
+
return path;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
function trimOutput(output) {
|
|
244
|
+
return {
|
|
245
|
+
stdout: output.stdout.trim().slice(0, 500),
|
|
246
|
+
stderr: output.stderr.trim().slice(0, 500),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
function summarize(checks) {
|
|
3
|
+
return {
|
|
4
|
+
ok: checks.filter((c) => c.status === "ok").length,
|
|
5
|
+
warnings: checks.filter((c) => c.status === "warning").length,
|
|
6
|
+
failures: checks.filter((c) => c.status === "fail").length,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
describe("doctor summarization", () => {
|
|
10
|
+
it("counts mixed statuses", () => {
|
|
11
|
+
const summary = summarize([
|
|
12
|
+
{ id: "a", label: "A", status: "ok", message: "" },
|
|
13
|
+
{ id: "b", label: "B", status: "warning", message: "" },
|
|
14
|
+
{ id: "c", label: "C", status: "fail", message: "" },
|
|
15
|
+
{ id: "d", label: "D", status: "ok", message: "" },
|
|
16
|
+
]);
|
|
17
|
+
expect(summary).toEqual({ ok: 2, warnings: 1, failures: 1 });
|
|
18
|
+
});
|
|
19
|
+
it("counts an all-ok run", () => {
|
|
20
|
+
const summary = summarize([
|
|
21
|
+
{ id: "a", label: "A", status: "ok", message: "" },
|
|
22
|
+
{ id: "b", label: "B", status: "ok", message: "" },
|
|
23
|
+
]);
|
|
24
|
+
expect(summary).toEqual({ ok: 2, warnings: 0, failures: 0 });
|
|
25
|
+
});
|
|
26
|
+
it("counts an empty check list", () => {
|
|
27
|
+
expect(summarize([])).toEqual({ ok: 0, warnings: 0, failures: 0 });
|
|
28
|
+
});
|
|
29
|
+
});
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,7 +1,56 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { configureAgentMcp, parseAgent, parseScope, supportedAgents, } from "../lib/agent-config.js";
|
|
2
3
|
import { startMcpServer } from "../mcp/server.js";
|
|
3
4
|
export const mcpCommand = new Command("mcp")
|
|
4
5
|
.description("Start the MCP server for AI tool integration (Cursor, Claude Code, etc.)")
|
|
5
6
|
.action(async () => {
|
|
6
7
|
await startMcpServer();
|
|
7
8
|
});
|
|
9
|
+
mcpCommand
|
|
10
|
+
.command("setup <agent>")
|
|
11
|
+
.description("Configure an AI agent to use the Summer Engine MCP server")
|
|
12
|
+
.option("--scope <scope>", "Configuration scope: user or project", "user")
|
|
13
|
+
.option("--print", "Print the MCP config snippet instead of writing files")
|
|
14
|
+
.option("--dry-run", "Show planned changes without writing files")
|
|
15
|
+
.option("--local-dev", "Use the local built CLI instead of npx summer-engine")
|
|
16
|
+
.option("--json", "Print the setup result as JSON")
|
|
17
|
+
.action(async (agentValue, opts) => {
|
|
18
|
+
const agent = parseAgent(agentValue);
|
|
19
|
+
if (!agent) {
|
|
20
|
+
throw new Error(`Unsupported agent. Use one of: ${supportedAgents.join(", ")}`);
|
|
21
|
+
}
|
|
22
|
+
const scope = parseScope(opts.scope);
|
|
23
|
+
if (!scope) {
|
|
24
|
+
throw new Error("Invalid --scope. Use user or project.");
|
|
25
|
+
}
|
|
26
|
+
const result = await configureAgentMcp({
|
|
27
|
+
agent,
|
|
28
|
+
scope,
|
|
29
|
+
print: opts.print,
|
|
30
|
+
dryRun: opts.dryRun,
|
|
31
|
+
localDev: opts.localDev,
|
|
32
|
+
});
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
console.log(JSON.stringify(result, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (result.print) {
|
|
38
|
+
console.log(result.snippet.trimEnd());
|
|
39
|
+
}
|
|
40
|
+
else if (result.dryRun) {
|
|
41
|
+
console.log(`Would update ${result.path} for ${result.agent} (${result.scope}).`);
|
|
42
|
+
console.log(result.snippet.trimEnd());
|
|
43
|
+
}
|
|
44
|
+
else if (result.wrote) {
|
|
45
|
+
console.log(`Configured Summer Engine MCP for ${result.agent} (${result.scope}) at ${result.path}.`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log(`Summer Engine MCP already configured for ${result.agent} (${result.scope}) at ${result.path}.`);
|
|
49
|
+
}
|
|
50
|
+
for (const warning of result.warnings) {
|
|
51
|
+
console.warn(`Warning: ${warning}`);
|
|
52
|
+
}
|
|
53
|
+
if (!result.print && !result.dryRun) {
|
|
54
|
+
console.log(result.nextSteps[result.nextSteps.length - 1]);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { configureAgentMcp, parseAgent, parseScope, supportedAgents, } from "../lib/agent-config.js";
|
|
4
|
+
import { printDoctorResult, runDoctor } from "./doctor.js";
|
|
5
|
+
export const setupCommand = new Command("setup")
|
|
6
|
+
.description("Configure Summer Engine for an AI agent and run diagnostics")
|
|
7
|
+
.argument("[agent]", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
8
|
+
.option("--agent <agent>", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
9
|
+
.option("--scope <scope>", "Configuration scope: user or project", "user")
|
|
10
|
+
.option("--dry-run", "Show planned changes without writing files")
|
|
11
|
+
.option("--print", "Print the MCP config snippet instead of writing files")
|
|
12
|
+
.option("--yes", "Apply practical setup steps without prompting")
|
|
13
|
+
.option("--json", "Print setup result as JSON")
|
|
14
|
+
.action(async (agentArg, opts) => {
|
|
15
|
+
const agent = resolveAgent(agentArg, opts.agent);
|
|
16
|
+
const scope = resolveScope(opts.scope);
|
|
17
|
+
const config = await configureAgentMcp({
|
|
18
|
+
agent,
|
|
19
|
+
scope,
|
|
20
|
+
dryRun: opts.dryRun,
|
|
21
|
+
print: opts.print,
|
|
22
|
+
});
|
|
23
|
+
const skills = setupRecommendedSkills(agent, {
|
|
24
|
+
dryRun: Boolean(opts.dryRun || opts.print),
|
|
25
|
+
yes: Boolean(opts.yes),
|
|
26
|
+
});
|
|
27
|
+
const doctor = await runDoctor({ quiet: true });
|
|
28
|
+
if (opts.json) {
|
|
29
|
+
console.log(JSON.stringify({
|
|
30
|
+
ok: doctor.ok && skills.status !== "failed",
|
|
31
|
+
config,
|
|
32
|
+
skills,
|
|
33
|
+
doctor,
|
|
34
|
+
}, null, 2));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
printSetupResult(config, skills, doctor);
|
|
38
|
+
}
|
|
39
|
+
if (!doctor.ok || skills.status === "failed") {
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
function resolveAgent(agentArg, agentOpt) {
|
|
44
|
+
if (agentArg && agentOpt && parseAgent(agentArg) !== parseAgent(agentOpt)) {
|
|
45
|
+
throw new Error("Specify the agent either positionally or with --agent, not both.");
|
|
46
|
+
}
|
|
47
|
+
const parsed = parseAgent(agentOpt ?? agentArg);
|
|
48
|
+
if (!parsed) {
|
|
49
|
+
throw new Error(`Specify an agent: ${supportedAgents.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
function resolveScope(scopeOpt) {
|
|
54
|
+
const parsed = parseScope(scopeOpt);
|
|
55
|
+
if (!parsed) {
|
|
56
|
+
throw new Error("Invalid --scope. Use user or project.");
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
function setupRecommendedSkills(agent, options) {
|
|
61
|
+
const invocation = skillInstallInvocation(agent);
|
|
62
|
+
if (!invocation) {
|
|
63
|
+
return {
|
|
64
|
+
status: "skipped",
|
|
65
|
+
message: "No automatic skill installer is available for this agent yet. Run `summer skills install --all` and point the agent at the installed SKILL.md files.",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (options.dryRun || !options.yes) {
|
|
69
|
+
return {
|
|
70
|
+
status: "planned",
|
|
71
|
+
command: invocation.display,
|
|
72
|
+
message: `Recommended skills can be installed with: ${invocation.display.join(" ")}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
76
|
+
env: process.env,
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
});
|
|
79
|
+
if (result.status === 0) {
|
|
80
|
+
return {
|
|
81
|
+
status: "installed",
|
|
82
|
+
command: invocation.display,
|
|
83
|
+
message: "Recommended Summer skills installed.",
|
|
84
|
+
stdout: result.stdout.trim(),
|
|
85
|
+
stderr: result.stderr.trim(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
status: "failed",
|
|
90
|
+
command: invocation.display,
|
|
91
|
+
message: `Recommended skill install failed with exit code ${result.status ?? "unknown"}.`,
|
|
92
|
+
stdout: result.stdout.trim(),
|
|
93
|
+
stderr: result.stderr.trim(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function skillInstallInvocation(agent) {
|
|
97
|
+
const cliPath = process.argv[1];
|
|
98
|
+
if (!cliPath)
|
|
99
|
+
return null;
|
|
100
|
+
const command = cliPath.endsWith(".js") ? process.execPath : cliPath;
|
|
101
|
+
const prefix = cliPath.endsWith(".js") ? [cliPath] : [];
|
|
102
|
+
const baseArgs = ["skills", "install", "--recommended", "--agent", agent];
|
|
103
|
+
return {
|
|
104
|
+
command,
|
|
105
|
+
args: [...prefix, ...baseArgs],
|
|
106
|
+
display: [cliPath, ...baseArgs],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function printSetupResult(config, skills, doctor) {
|
|
110
|
+
if (config.print) {
|
|
111
|
+
console.log(config.snippet.trimEnd());
|
|
112
|
+
}
|
|
113
|
+
else if (config.dryRun) {
|
|
114
|
+
console.log(`Would update ${config.path} for ${config.agent} (${config.scope}).`);
|
|
115
|
+
}
|
|
116
|
+
else if (config.wrote) {
|
|
117
|
+
console.log(`Configured Summer Engine MCP for ${config.agent} (${config.scope}) at ${config.path}.`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(`Summer Engine MCP already configured for ${config.agent} (${config.scope}) at ${config.path}.`);
|
|
121
|
+
}
|
|
122
|
+
for (const warning of config.warnings) {
|
|
123
|
+
console.warn(`Warning: ${warning}`);
|
|
124
|
+
}
|
|
125
|
+
console.log(`Skills: ${skills.message}`);
|
|
126
|
+
console.log("");
|
|
127
|
+
printDoctorResult(doctor);
|
|
128
|
+
}
|