summer-engine 4.5.0 → 4.6.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.factory-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/cli/commands/setup.js +175 -32
- package/dist/installer/agent-config.d.ts +29 -0
- package/dist/installer/agent-config.js +75 -11
- package/dist/installer/agent-detect.d.ts +44 -0
- package/dist/installer/agent-detect.js +166 -0
- package/dist/installer/agent-table.d.ts +19 -0
- package/dist/installer/agent-table.js +130 -0
- package/dist/installer/setup-error.d.ts +19 -0
- package/dist/installer/setup-error.js +34 -0
- package/dist/installer/version-check.d.ts +5 -0
- package/dist/installer/version-check.js +21 -0
- package/dist/mcp/ops-surface.d.ts +7 -0
- package/dist/mcp/ops-surface.js +12 -1
- package/dist/project-memory/agent-instructions.js +11 -3
- package/gemini-extension.json +1 -1
- package/library/skills/make-game/SKILL.md +4 -4
- package/package.json +1 -1
- package/registry/generated/gemini-extension.json +1 -1
- package/registry/generated/index.json +1 -1
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +2 -2
- package/registry/generated/plugin.claude.json +1 -1
- package/registry/generated/plugin.codex.json +1 -1
- package/registry/generated/plugin.cursor.json +1 -1
- package/registry/generated/plugin.factory.json +1 -1
- package/registry/generated/server.json +2 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "summer",
|
|
12
12
|
"source": "./",
|
|
13
|
-
"version": "4.
|
|
13
|
+
"version": "4.6.0",
|
|
14
14
|
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. A first-run guide (/summer:start), game-dev skills, and two MCP servers: the bridge to the Summer Engine desktop app, and Summer's hosted cloud server for people without it.",
|
|
15
15
|
"author": {
|
|
16
16
|
"name": "Summer Engine",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "summer",
|
|
4
4
|
"displayName": "Summer Engine",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.6.0",
|
|
6
6
|
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. A first-run guide (/summer:start), game-dev skills, and two MCP servers: the bridge to the Summer Engine desktop app, and Summer's hosted cloud server for people without it.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Summer Engine",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/codex — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.6.0",
|
|
5
5
|
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"_generated": "GENERATED from integrations/cursor — do not edit; npm run generate:registry",
|
|
4
4
|
"name": "summer",
|
|
5
5
|
"displayName": "Summer Engine",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.6.0",
|
|
7
7
|
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Summer Engine",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_generated": "GENERATED from integrations/factory — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
4
|
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.6.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Summer Engine",
|
|
8
8
|
"email": "founders@summerengine.com",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to summer-engine will be documented here. Following [Keep a
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [4.6.0] - 2026-10-21
|
|
8
|
+
|
|
9
|
+
A first game that looks finished, and `summer setup` for the Summer desktop app's one-click agent connect.
|
|
10
|
+
|
|
11
|
+
- **`summer setup` for installers: find the agents, read each step, run a bundled Node.js.** For the Summer desktop app's installer, which connects the person's coding agent in one click and ships its own Node.js (Claude Code and Codex install without one).
|
|
12
|
+
- **`summer setup --detect --json`** lists every supported agent: whether it is installed (`installed`, `programFound` and each hit in `detectedBy`: a command on PATH or in the usual install folders, a macOS app or Windows program, the agent's own folder, a VS Code extension), its MCP config file, and whether that file already lists `summer-engine`. It writes nothing.
|
|
13
|
+
- **`summer setup <agent> --yes --json` reports each step** in `steps`: the MCP entry, Summer Games, skills, project files and doctor, each with `ok`, a `status` and, on failure, an `error` with a `code` (`config_invalid`, `config_not_writable`, `skills_install_failed`, `runtime_not_found`, ...). A failed step no longer stops the others, and any failure exits 1. An error before setup starts (an unknown agent, a bad option) is one JSON document too. The output for people is unchanged.
|
|
14
|
+
- **`--runtime <node> --package-dir <dir>`** writes the MCP entry as `<node> <dir>/dist/bin/summer.js mcp` instead of `npx`, in every agent's file format. Setup checks that both exist and that the folder is the summer-engine package. Doctor's skills refresh keeps the bundled runtime.
|
|
15
|
+
- **First game: make it look finished.** A first game used to come out as grey boxes: the `make-game` skill told agents to build the first playable from shapes and add art later, and nothing an agent always reads said anything about looks. Now the rule sits where agents read it without choosing to:
|
|
16
|
+
- **MCP server instructions** open with it, inside the first 512 characters (Codex keeps the first 512): keep the features the person asked for and raise the look (sky and sun with shadows, fog, a texture on every surface, real models from the starter project and `summer_assets_search` first, never capsules or boxes as final art, one palette, a styled UI font), then play it and check a screenshot of the running game. They no longer open with "call summer_get_project_context first".
|
|
17
|
+
- **The project's `AGENTS.md` block** (written by `summer create` and `summer setup --yes`) has a "First game: make it look finished" section. It now says to write GDScript files directly and to create scenes and import assets through Summer, and its publishing line matches the store: a game goes live when Summer's automated checks pass, with no wait for a review.
|
|
18
|
+
- **`make-game`:** the first playable already looks good; generation still never blocks the first play (starter and library art first).
|
|
19
|
+
|
|
7
20
|
## [4.5.0] - 2026-10-19
|
|
8
21
|
|
|
9
22
|
4.4.0 was not published to npm. 4.5.0 is the first release with everything in it (the [4.4.0] section below), plus these changes.
|
|
@@ -2,8 +2,10 @@ import { spawnSync } from "child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { relative } from "path";
|
|
4
4
|
import { Command, Option } from "commander";
|
|
5
|
-
import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
|
|
5
|
+
import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveBundledRuntime, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
|
|
6
6
|
import { previewNote, setupSkills } from "../../installer/setup.js";
|
|
7
|
+
import { detectAgents } from "../../installer/agent-detect.js";
|
|
8
|
+
import { SetupError, toStepError } from "../../installer/setup-error.js";
|
|
7
9
|
import { printDoctorResult, runDoctor } from "../../core/capabilities/doctor.js";
|
|
8
10
|
import { brandLine, c, sym, tildeify } from "../../core/format.js";
|
|
9
11
|
import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
|
|
@@ -23,6 +25,8 @@ const MCP_ENTRY_FLAGS = [
|
|
|
23
25
|
["localDev", "--local-dev"],
|
|
24
26
|
["channel", "--channel"],
|
|
25
27
|
["profile", "--profile"],
|
|
28
|
+
["runtime", "--runtime"],
|
|
29
|
+
["packageDir", "--package-dir"],
|
|
26
30
|
];
|
|
27
31
|
export const setupCommand = new Command("setup")
|
|
28
32
|
.description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games). --cli installs the skills only: the agent runs summer commands in its shell, with no MCP server.")
|
|
@@ -32,6 +36,9 @@ export const setupCommand = new Command("setup")
|
|
|
32
36
|
.option("--dry-run", "Show planned changes without writing files")
|
|
33
37
|
.option("--print", "Print the MCP config snippet instead of writing files")
|
|
34
38
|
.option("--local-dev", "Point the agent at this checkout's built CLI (node <repo>/dist/bin/summer.js mcp) instead of npx summer-engine@latest — for testing unpublished builds. SUMMER_DEV=1 does the same.")
|
|
39
|
+
.option("--runtime <node>", "Run the MCP server with this node executable instead of npx: <node> <package-dir>/dist/bin/summer.js mcp. Needs --package-dir. For installers that ship their own Node.js")
|
|
40
|
+
.option("--package-dir <dir>", "The summer-engine package folder (package.json and dist/bin/summer.js) that --runtime runs")
|
|
41
|
+
.option("--detect", "List every supported agent: installed or not, its MCP config file, and whether it already lists summer-engine. Writes nothing; takes no agent. Use with --json")
|
|
35
42
|
.option("--channel <dist-tag>", "npm dist-tag or exact version the agent's MCP entry runs: npx -y summer-engine@<dist-tag> mcp (default latest; a CLI newer than npm latest pins next when next is this version, else this version). SUMMER_CHANNEL does the same.")
|
|
36
43
|
.option("--yes", "Apply practical setup steps without prompting")
|
|
37
44
|
.option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games)")
|
|
@@ -49,8 +56,52 @@ export const setupCommand = new Command("setup")
|
|
|
49
56
|
// Pre-reversal opt-in, kept one release as a hidden no-op so old scripts still parse.
|
|
50
57
|
.addOption(new Option("--include-preview", "No-op: preview skills install by default").hideHelp())
|
|
51
58
|
.action(async (agentArg, opts) => {
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
if (opts.detect) {
|
|
60
|
+
runDetect(agentArg, opts);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!opts.json) {
|
|
64
|
+
await setupAgent(agentArg, opts);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// --json: one JSON document on stdout, also when setup cannot start.
|
|
68
|
+
try {
|
|
69
|
+
await setupAgent(agentArg, opts);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.log(JSON.stringify({ ok: false, error: toStepError(error, "setup_failed"), steps: [] }, null, 2));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Runs one step. With --json a throw becomes the step's coded error and the
|
|
78
|
+
* other steps still run; without it the throw ends setup as it always did.
|
|
79
|
+
*/
|
|
80
|
+
async function runStep(json, fallbackCode, run) {
|
|
81
|
+
if (!json)
|
|
82
|
+
return { value: await run() };
|
|
83
|
+
try {
|
|
84
|
+
return { value: await run() };
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return { value: null, error: toStepError(error, fallbackCode) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function setupAgent(agentArg, opts) {
|
|
91
|
+
let agent;
|
|
92
|
+
try {
|
|
93
|
+
agent = resolveAgentSelection(agentArg, opts.agent);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw new SetupError("unknown_agent", error instanceof Error ? error.message : String(error));
|
|
97
|
+
}
|
|
98
|
+
let scope;
|
|
99
|
+
try {
|
|
100
|
+
scope = resolveConfigScope(opts.scope);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new SetupError("invalid_scope", error instanceof Error ? error.message : String(error));
|
|
104
|
+
}
|
|
54
105
|
if (opts.removeAllowRules) {
|
|
55
106
|
await removeCliAllowRules(agent, scope, opts);
|
|
56
107
|
return;
|
|
@@ -63,8 +114,14 @@ export const setupCommand = new Command("setup")
|
|
|
63
114
|
if (opts.allowRules)
|
|
64
115
|
console.error(` ${sym.warn()} ${c.yellow("--allow-rules works with --cli only (the MCP server asks through the agent's own tool approvals); no rules were added.")}`);
|
|
65
116
|
const profile = resolveSetupProfile(opts);
|
|
66
|
-
const
|
|
67
|
-
|
|
117
|
+
const runtime = resolveBundledRuntime(opts.runtime, opts.packageDir);
|
|
118
|
+
if (runtime && (opts.localDev || opts.channel)) {
|
|
119
|
+
throw new SetupError("invalid_option", `--runtime runs a bundled summer-engine; leave out ${opts.localDev ? "--local-dev" : "--channel"}.`);
|
|
120
|
+
}
|
|
121
|
+
const json = Boolean(opts.json);
|
|
122
|
+
// A bundled runtime runs the package beside it, so npm's tags do not matter (and no network call is made).
|
|
123
|
+
const defaultChannel = runtime ? { channel: "latest" } : await resolveDefaultChannel(cliVersion);
|
|
124
|
+
const mcp = await runStep(json, "mcp_entry_failed", () => configureAgentMcp({
|
|
68
125
|
agent,
|
|
69
126
|
scope,
|
|
70
127
|
dryRun: opts.dryRun,
|
|
@@ -72,14 +129,19 @@ export const setupCommand = new Command("setup")
|
|
|
72
129
|
localDev: Boolean(opts.localDev) || process.env.SUMMER_DEV === "1",
|
|
73
130
|
channel: opts.channel ?? process.env.SUMMER_CHANNEL ?? defaultChannel.channel,
|
|
74
131
|
profile,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
132
|
+
runtime: runtime ?? undefined,
|
|
133
|
+
}));
|
|
134
|
+
const config = mcp.value;
|
|
135
|
+
const gamesSkipped = opts.games === false || opts.assetsOnly || opts.print;
|
|
136
|
+
const gamesStep = gamesSkipped
|
|
137
|
+
? { value: null }
|
|
138
|
+
: await runStep(json, "summer_games_failed", () => configureSummerGamesMcp({ agent, scope, dryRun: opts.dryRun }));
|
|
139
|
+
const games = gamesStep.value;
|
|
140
|
+
if (!runtime && !opts.channel && !process.env.SUMMER_CHANNEL && defaultChannel.note && !opts.print) {
|
|
141
|
+
// On stderr with --json, so stdout stays one JSON document.
|
|
142
|
+
(json ? console.error : console.log)(` ${c.dim(defaultChannel.note)}`);
|
|
143
|
+
}
|
|
144
|
+
const skillsStep = await runStep(json, "skills_install_failed", () => setupSkills(agent, {
|
|
83
145
|
dryRun: Boolean(opts.dryRun || opts.print),
|
|
84
146
|
yes: Boolean(opts.yes),
|
|
85
147
|
force: Boolean(opts.force),
|
|
@@ -87,26 +149,104 @@ export const setupCommand = new Command("setup")
|
|
|
87
149
|
recommended: Boolean(opts.recommended),
|
|
88
150
|
stableOnly: Boolean(opts.stableOnly),
|
|
89
151
|
assetsOnly: Boolean(opts.assetsOnly),
|
|
90
|
-
});
|
|
91
|
-
const
|
|
152
|
+
}));
|
|
153
|
+
const skills = skillsStep.value ?? { status: "failed", message: skillsStep.error?.message ?? "Skill install failed." };
|
|
154
|
+
const projectStep = await runStep(json, "project_files_failed", () => setupProjectInstructions(opts));
|
|
155
|
+
const projectFiles = projectStep.value;
|
|
92
156
|
const doctor = await runDoctor({ quiet: true });
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
if (json) {
|
|
158
|
+
const steps = [
|
|
159
|
+
mcpEntryStep(config, mcp.error),
|
|
160
|
+
summerGamesStep(agent, games, gamesSkipped ? "skipped" : null, gamesStep.error, Boolean(opts.dryRun)),
|
|
161
|
+
skillsStepResult(skills, skillsStep.error),
|
|
162
|
+
projectFilesStep(projectFiles, projectStep.error, opts),
|
|
163
|
+
doctorStep(doctor),
|
|
164
|
+
];
|
|
165
|
+
const ok = steps.every((step) => step.ok);
|
|
166
|
+
console.log(JSON.stringify({ ok, agent, scope, steps, config, games, skills, projectFiles, doctor }, null, 2));
|
|
167
|
+
if (!ok)
|
|
168
|
+
process.exit(1);
|
|
169
|
+
return;
|
|
105
170
|
}
|
|
171
|
+
printSetupResult(config, games, skills, doctor, projectFiles);
|
|
106
172
|
if (!doctor.ok || skills.status === "failed") {
|
|
107
173
|
process.exit(1);
|
|
108
174
|
}
|
|
109
|
-
}
|
|
175
|
+
}
|
|
176
|
+
function mcpEntryStep(config, error) {
|
|
177
|
+
if (!config)
|
|
178
|
+
return { step: "mcp_entry", ok: false, status: "failed", error: error ?? { code: "mcp_entry_failed", message: "The MCP entry was not written." } };
|
|
179
|
+
const status = config.print ? "printed" : config.dryRun ? (config.changed ? "planned" : "unchanged") : config.wrote ? "written" : "unchanged";
|
|
180
|
+
return { step: "mcp_entry", ok: true, status, path: config.path, ...(config.warnings.length ? { warnings: config.warnings } : {}) };
|
|
181
|
+
}
|
|
182
|
+
function summerGamesStep(agent, games, skipped, error, dryRun) {
|
|
183
|
+
if (error)
|
|
184
|
+
return { step: "summer_games", ok: false, status: "failed", error };
|
|
185
|
+
if (skipped)
|
|
186
|
+
return { step: "summer_games", ok: true, status: "skipped" };
|
|
187
|
+
// configureSummerGamesMcp returns null only for agents without a Summer Games entry shape.
|
|
188
|
+
if (!games)
|
|
189
|
+
return { step: "summer_games", ok: true, status: "not_supported", warnings: [`${agentLabel(agent)} gets no Summer Games entry from setup.`] };
|
|
190
|
+
const status = games.existingName ? "exists" : dryRun ? (games.changed ? "planned" : "unchanged") : games.wrote ? "written" : "unchanged";
|
|
191
|
+
return { step: "summer_games", ok: true, status, path: games.path };
|
|
192
|
+
}
|
|
193
|
+
function skillsStepResult(skills, error) {
|
|
194
|
+
if (skills.status === "failed") {
|
|
195
|
+
return {
|
|
196
|
+
step: "skills",
|
|
197
|
+
ok: false,
|
|
198
|
+
status: "failed",
|
|
199
|
+
error: error ?? { code: "skills_install_failed", message: [skills.message, skills.stderr].filter(Boolean).join(" ") },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { step: "skills", ok: true, status: skills.status, ...(skills.destination ? { path: skills.destination } : {}) };
|
|
203
|
+
}
|
|
204
|
+
function projectFilesStep(project, error, opts) {
|
|
205
|
+
if (error)
|
|
206
|
+
return { step: "project_files", ok: false, status: "failed", error };
|
|
207
|
+
if (!project) {
|
|
208
|
+
const skipped = opts.projectFiles === false || opts.print || opts.assetsOnly;
|
|
209
|
+
return { step: "project_files", ok: true, status: skipped ? "skipped" : "not_in_project" };
|
|
210
|
+
}
|
|
211
|
+
// A file setup leaves alone (a link out of the project, a broken Summer block) is a warning, as in the human output.
|
|
212
|
+
const warnings = project.files.filter((file) => file.reason).map((file) => `${file.path}: ${file.reason}`);
|
|
213
|
+
return { step: "project_files", ok: true, status: project.written ? "written" : "planned", path: project.root, ...(warnings.length ? { warnings } : {}) };
|
|
214
|
+
}
|
|
215
|
+
function doctorStep(doctor) {
|
|
216
|
+
if (doctor.ok)
|
|
217
|
+
return { step: "doctor", ok: true, status: "passed" };
|
|
218
|
+
const failed = doctor.checks.filter((check) => check.status === "fail");
|
|
219
|
+
return {
|
|
220
|
+
step: "doctor",
|
|
221
|
+
ok: false,
|
|
222
|
+
status: "failed",
|
|
223
|
+
error: { code: "doctor_failed", message: failed.map((check) => `${check.label}: ${check.message}`).join("; ") || "Doctor found a problem." },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** `summer setup --detect`: every agent, installed or not, and whether it is linked. Writes nothing. */
|
|
227
|
+
function runDetect(agentArg, opts) {
|
|
228
|
+
if (agentArg || opts.agent) {
|
|
229
|
+
const message = "--detect lists every agent; leave out the agent name.";
|
|
230
|
+
if (!opts.json)
|
|
231
|
+
throw new Error(message);
|
|
232
|
+
console.log(JSON.stringify({ ok: false, error: { code: "invalid_option", message }, agents: [] }, null, 2));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const agents = detectAgents();
|
|
237
|
+
if (opts.json) {
|
|
238
|
+
console.log(JSON.stringify({ ok: true, os: process.platform, agents }, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.log("");
|
|
242
|
+
for (const agent of agents) {
|
|
243
|
+
if (agent.status === "legacy" && !agent.installed)
|
|
244
|
+
continue;
|
|
245
|
+
const mark = agent.installed ? sym.ok() : c.dim("-");
|
|
246
|
+
const linked = agent.mcpEntry ? c.dim(`linked ${tildeify(agent.configPath)}`) : agent.installed ? c.dim(`not linked: summer setup ${agent.id}`) : c.dim("not found");
|
|
247
|
+
console.log(` ${mark} ${agent.label} ${linked}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
110
250
|
/**
|
|
111
251
|
* `summer setup <agent> --cli`: the skills (the asset skills with --assets-only)
|
|
112
252
|
* and no MCP server, so the agent runs Summer as `summer <area> <action>` in its
|
|
@@ -117,7 +257,7 @@ async function setupCliOnly(agent, scope, opts) {
|
|
|
117
257
|
const spec = agentSpec(agent);
|
|
118
258
|
const mcpFlags = MCP_ENTRY_FLAGS.filter(([key]) => opts[key] !== undefined && opts[key] !== false).map(([, flag]) => flag);
|
|
119
259
|
if (mcpFlags.length > 0) {
|
|
120
|
-
throw new
|
|
260
|
+
throw new SetupError("invalid_option", `${mcpFlags.join(", ")} sets up the MCP entry, and --cli writes none. Leave ${mcpFlags.length === 1 ? "it" : "them"} out, or leave out --cli.`);
|
|
121
261
|
}
|
|
122
262
|
const refusal = cliSetupRefusal(spec);
|
|
123
263
|
if (refusal)
|
|
@@ -278,11 +418,11 @@ async function recordedMcpEntry(agent, scope) {
|
|
|
278
418
|
function resolveSetupProfile(opts) {
|
|
279
419
|
const profile = opts.profile === undefined ? undefined : parseProfile(opts.profile);
|
|
280
420
|
if (profile === null) {
|
|
281
|
-
throw new
|
|
421
|
+
throw new SetupError("invalid_option", `Unknown --profile "${opts.profile}". Use one of: ${Object.keys(PROFILES).join(", ")}.`);
|
|
282
422
|
}
|
|
283
423
|
if (opts.assetsOnly) {
|
|
284
424
|
if (profile && profile !== "assets")
|
|
285
|
-
throw new
|
|
425
|
+
throw new SetupError("invalid_option", "--assets-only uses the assets profile; leave out --profile.");
|
|
286
426
|
return "assets";
|
|
287
427
|
}
|
|
288
428
|
return profile;
|
|
@@ -305,7 +445,10 @@ function printSetupResult(config, games, skills, doctor, projectFiles) {
|
|
|
305
445
|
else {
|
|
306
446
|
console.log(` ${sym.ok()} Already linked to ${c.bold(label)} ${c.dim(tildeify(config.path))}`);
|
|
307
447
|
}
|
|
308
|
-
if (config.
|
|
448
|
+
if (config.runtime) {
|
|
449
|
+
console.log(` ${c.dim("(bundled runtime)")} MCP server command: ${c.dim(`${config.server.command} ${config.server.args.join(" ")}`)}`);
|
|
450
|
+
}
|
|
451
|
+
else if (config.localDev) {
|
|
309
452
|
console.log(` ${c.dim("(local dev)")} MCP server command: ${c.dim(`${config.server.command} ${config.server.args.join(" ")}`)}`);
|
|
310
453
|
}
|
|
311
454
|
else if (config.channel !== "latest") {
|
|
@@ -18,6 +18,10 @@ export interface AgentConfigOptions {
|
|
|
18
18
|
channel?: string;
|
|
19
19
|
/** MCP tool profile written as SUMMER_PROFILE; default the agent's own (full for most). */
|
|
20
20
|
profile?: Profile;
|
|
21
|
+
/** A bundled Node runtime and summer-engine package (the Summer desktop installer):
|
|
22
|
+
* the entry runs `<node> <packageDir>/dist/bin/summer.js mcp` instead of npx.
|
|
23
|
+
* Check it with resolveBundledRuntime first. Ignores localDev and channel. */
|
|
24
|
+
runtime?: BundledRuntime;
|
|
21
25
|
cwd?: string;
|
|
22
26
|
env?: NodeJS.ProcessEnv;
|
|
23
27
|
}
|
|
@@ -39,6 +43,8 @@ export interface AgentConfigResult {
|
|
|
39
43
|
channel: string;
|
|
40
44
|
/** The MCP tool profile the entry runs (full = every tool, no SUMMER_PROFILE written). */
|
|
41
45
|
profile: Profile;
|
|
46
|
+
/** Set when the entry runs a bundled Node runtime (--runtime, --package-dir). */
|
|
47
|
+
runtime: BundledRuntime | null;
|
|
42
48
|
warnings: string[];
|
|
43
49
|
nextSteps: string[];
|
|
44
50
|
}
|
|
@@ -89,6 +95,29 @@ export declare function normalizeChannel(channel: string | undefined): string;
|
|
|
89
95
|
*/
|
|
90
96
|
export declare function withProfile(server: StdioMcpServerConfig, profile: Profile): StdioMcpServerConfig;
|
|
91
97
|
export declare function createSummerMcpServerConfig(localDev: boolean, platform?: NodeJS.Platform, channel?: string): StdioMcpServerConfig;
|
|
98
|
+
/**
|
|
99
|
+
* A Node runtime and a summer-engine package shipped next to each other (the
|
|
100
|
+
* Summer desktop installer bundles both, so the person needs no Node.js of
|
|
101
|
+
* their own). Both are absolute paths.
|
|
102
|
+
*/
|
|
103
|
+
export interface BundledRuntime {
|
|
104
|
+
/** The node executable (node, or node.exe on Windows). */
|
|
105
|
+
node: string;
|
|
106
|
+
/** The summer-engine package folder: the one with package.json and dist/bin/summer.js. */
|
|
107
|
+
packageDir: string;
|
|
108
|
+
}
|
|
109
|
+
/** The CLI entry point inside a summer-engine package folder. */
|
|
110
|
+
export declare function bundledCliPath(packageDir: string): string;
|
|
111
|
+
/**
|
|
112
|
+
* --runtime <node> and --package-dir <dir>: both or neither, and both must
|
|
113
|
+
* exist. The runtime must be an executable file (any file on Windows); the
|
|
114
|
+
* package folder must hold summer-engine's package.json and its built CLI.
|
|
115
|
+
* Returns absolute paths, so the agent's entry works from any folder.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveBundledRuntime(node: string | undefined, packageDir: string | undefined, cwd?: string): BundledRuntime | null;
|
|
118
|
+
/** The entry for a bundled runtime: `<node> <packageDir>/dist/bin/summer.js mcp`.
|
|
119
|
+
* node is a real executable, so no cmd.exe wrapper is needed on Windows. */
|
|
120
|
+
export declare function bundledRuntimeServerConfig(runtime: BundledRuntime): StdioMcpServerConfig;
|
|
92
121
|
export declare function renderConfigSnippet(agent: SupportedAgent, server: StdioMcpServerConfig): string;
|
|
93
122
|
/** The key under which the file lists MCP servers (see McpFormat in agent-table.ts). */
|
|
94
123
|
export declare function topLevelKey(format: McpFormat): string;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
1
|
+
import { accessSync, constants as fsConstants, existsSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import { copyFile, mkdir, readFile, writeFile } from "fs/promises";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { Document, isMap, parseDocument } from "yaml";
|
|
6
6
|
import { PACKAGE_ROOT } from "../core/package-root.js";
|
|
7
|
+
import { SetupError } from "./setup-error.js";
|
|
7
8
|
import { agentAliasMap, agentSpec, defaultPathContext, resolveMcpPath, supportedAgents, } from "./agent-table.js";
|
|
8
9
|
export { supportedAgents } from "./agent-table.js";
|
|
9
10
|
export const SUMMER_MCP_SERVER_NAME = "summer-engine";
|
|
@@ -50,7 +51,8 @@ export async function configureAgentMcp(options) {
|
|
|
50
51
|
const channel = normalizeChannel(options.channel);
|
|
51
52
|
const spec = agentSpec(options.agent);
|
|
52
53
|
const profile = options.profile ?? spec.mcpProfile ?? "full";
|
|
53
|
-
const
|
|
54
|
+
const runtime = options.runtime ?? null;
|
|
55
|
+
const server = withProfile(runtime ? bundledRuntimeServerConfig(runtime) : createSummerMcpServerConfig(Boolean(options.localDev), process.platform, channel), profile);
|
|
54
56
|
const target = resolveConfigTarget(spec, options.scope, cwd, env);
|
|
55
57
|
// An entry an earlier setup wrote where the agent no longer reads (Devin Desktop moved from ~/.codeium/windsurf).
|
|
56
58
|
const moved = spec.mcp.movedFrom ? spec.mcp.movedFrom(defaultPathContext(env, cwd)) : null;
|
|
@@ -76,9 +78,10 @@ export async function configureAgentMcp(options) {
|
|
|
76
78
|
wrote: shouldWrite && update.changed,
|
|
77
79
|
dryRun,
|
|
78
80
|
print,
|
|
79
|
-
localDev: Boolean(options.localDev),
|
|
81
|
+
localDev: !runtime && Boolean(options.localDev),
|
|
80
82
|
channel,
|
|
81
83
|
profile,
|
|
84
|
+
runtime,
|
|
82
85
|
warnings: target.warnings,
|
|
83
86
|
nextSteps: [`Updated ${target.path}.`, spec.restart],
|
|
84
87
|
};
|
|
@@ -320,6 +323,67 @@ export function createSummerMcpServerConfig(localDev, platform = process.platfor
|
|
|
320
323
|
args: ["-y", spec, "mcp"],
|
|
321
324
|
};
|
|
322
325
|
}
|
|
326
|
+
/** The CLI entry point inside a summer-engine package folder. */
|
|
327
|
+
export function bundledCliPath(packageDir) {
|
|
328
|
+
return join(packageDir, "dist", "bin", "summer.js");
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* --runtime <node> and --package-dir <dir>: both or neither, and both must
|
|
332
|
+
* exist. The runtime must be an executable file (any file on Windows); the
|
|
333
|
+
* package folder must hold summer-engine's package.json and its built CLI.
|
|
334
|
+
* Returns absolute paths, so the agent's entry works from any folder.
|
|
335
|
+
*/
|
|
336
|
+
export function resolveBundledRuntime(node, packageDir, cwd = process.cwd()) {
|
|
337
|
+
if (node === undefined && packageDir === undefined)
|
|
338
|
+
return null;
|
|
339
|
+
if (!node || !packageDir) {
|
|
340
|
+
throw new SetupError("runtime_incomplete", "--runtime and --package-dir go together: name the node executable and the summer-engine package folder.");
|
|
341
|
+
}
|
|
342
|
+
const nodePath = resolve(cwd, node);
|
|
343
|
+
const nodeStat = statIfExists(nodePath);
|
|
344
|
+
if (!nodeStat)
|
|
345
|
+
throw new SetupError("runtime_not_found", `--runtime ${nodePath} does not exist.`);
|
|
346
|
+
if (!nodeStat.isFile())
|
|
347
|
+
throw new SetupError("runtime_not_found", `--runtime ${nodePath} is not a file. Name the node executable itself.`);
|
|
348
|
+
if (process.platform !== "win32") {
|
|
349
|
+
try {
|
|
350
|
+
accessSync(nodePath, fsConstants.X_OK);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
throw new SetupError("runtime_not_executable", `--runtime ${nodePath} is not executable.`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const dir = resolve(cwd, packageDir);
|
|
357
|
+
if (!statIfExists(dir)?.isDirectory())
|
|
358
|
+
throw new SetupError("package_dir_not_found", `--package-dir ${dir} is not a folder.`);
|
|
359
|
+
let name;
|
|
360
|
+
try {
|
|
361
|
+
name = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")).name;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
name = undefined;
|
|
365
|
+
}
|
|
366
|
+
if (name !== "summer-engine") {
|
|
367
|
+
throw new SetupError("package_dir_invalid", `--package-dir ${dir} is not the summer-engine package: it has no package.json named summer-engine.`);
|
|
368
|
+
}
|
|
369
|
+
if (!statIfExists(bundledCliPath(dir))?.isFile()) {
|
|
370
|
+
throw new SetupError("package_dir_invalid", `--package-dir ${dir} has no built CLI at dist/bin/summer.js.`);
|
|
371
|
+
}
|
|
372
|
+
return { node: nodePath, packageDir: dir };
|
|
373
|
+
}
|
|
374
|
+
/** The entry for a bundled runtime: `<node> <packageDir>/dist/bin/summer.js mcp`.
|
|
375
|
+
* node is a real executable, so no cmd.exe wrapper is needed on Windows. */
|
|
376
|
+
export function bundledRuntimeServerConfig(runtime) {
|
|
377
|
+
return { command: runtime.node, args: [bundledCliPath(runtime.packageDir), "mcp"] };
|
|
378
|
+
}
|
|
379
|
+
function statIfExists(path) {
|
|
380
|
+
try {
|
|
381
|
+
return statSync(path);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
323
387
|
export function renderConfigSnippet(agent, server) {
|
|
324
388
|
const format = agentSpec(agent).format;
|
|
325
389
|
if (format === "toml")
|
|
@@ -449,14 +513,14 @@ async function upsertYamlConfig(path, keyPath, entry, write) {
|
|
|
449
513
|
// would make the root a flow mapping and the whole file would render as {}.
|
|
450
514
|
const doc = current.trim() === "" ? new Document({}) : parseDocument(current);
|
|
451
515
|
if (doc.errors.length > 0) {
|
|
452
|
-
throw new
|
|
516
|
+
throw new SetupError("config_invalid", `Could not parse YAML in ${path}: ${doc.errors[0].message}`);
|
|
453
517
|
}
|
|
454
518
|
if (!isMap(doc.contents)) {
|
|
455
|
-
throw new
|
|
519
|
+
throw new SetupError("config_invalid", `Expected ${path} to contain a YAML mapping.`);
|
|
456
520
|
}
|
|
457
521
|
const parent = doc.getIn(keyPath.slice(0, -1));
|
|
458
522
|
if (parent !== undefined && parent !== null && !isMap(parent)) {
|
|
459
|
-
throw new
|
|
523
|
+
throw new SetupError("config_invalid", `Existing ${keyPath[0]} value in ${path} must be a mapping.`);
|
|
460
524
|
}
|
|
461
525
|
doc.setIn(keyPath, doc.createNode(entry));
|
|
462
526
|
const next = doc.toString();
|
|
@@ -474,7 +538,7 @@ async function readJsonConfig(path) {
|
|
|
474
538
|
content = await readFile(path, "utf-8");
|
|
475
539
|
}
|
|
476
540
|
catch (error) {
|
|
477
|
-
throw new
|
|
541
|
+
throw new SetupError("config_unreadable", `Could not read ${path}: ${formatError(error)}`);
|
|
478
542
|
}
|
|
479
543
|
if (content.trim() === "")
|
|
480
544
|
return {};
|
|
@@ -483,10 +547,10 @@ async function readJsonConfig(path) {
|
|
|
483
547
|
parsed = JSON.parse(content);
|
|
484
548
|
}
|
|
485
549
|
catch (error) {
|
|
486
|
-
throw new
|
|
550
|
+
throw new SetupError("config_invalid", `Could not parse JSON in ${path}: ${formatError(error)}`);
|
|
487
551
|
}
|
|
488
552
|
if (!isJsonObject(parsed)) {
|
|
489
|
-
throw new
|
|
553
|
+
throw new SetupError("config_invalid", `Expected ${path} to contain a JSON object.`);
|
|
490
554
|
}
|
|
491
555
|
return parsed;
|
|
492
556
|
}
|
|
@@ -662,7 +726,7 @@ async function readTextFileIfExists(path) {
|
|
|
662
726
|
return await readFile(path, "utf-8");
|
|
663
727
|
}
|
|
664
728
|
catch (error) {
|
|
665
|
-
throw new
|
|
729
|
+
throw new SetupError("config_unreadable", `Could not read ${path}: ${formatError(error)}`);
|
|
666
730
|
}
|
|
667
731
|
}
|
|
668
732
|
function upsertTomlTable(content, table, name = SUMMER_MCP_SERVER_NAME) {
|
|
@@ -716,7 +780,7 @@ function copyJsonObject(value) {
|
|
|
716
780
|
}
|
|
717
781
|
function mergeNamedObject(value, key, entry, label) {
|
|
718
782
|
if (value !== undefined && !isJsonObject(value)) {
|
|
719
|
-
throw new
|
|
783
|
+
throw new SetupError("config_invalid", `Existing ${label} value must be a JSON object.`);
|
|
720
784
|
}
|
|
721
785
|
return {
|
|
722
786
|
...(isJsonObject(value) ? value : {}),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ConfigScope, type McpFormat, type PathContext, type SupportedAgent } from "./agent-table.js";
|
|
2
|
+
import { type SetupStepError } from "./setup-error.js";
|
|
3
|
+
import type { StdioMcpServerConfig } from "./agent-config.js";
|
|
4
|
+
export interface DetectHit {
|
|
5
|
+
kind: "bin" | "app" | "folder" | "extension";
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DetectedAgent {
|
|
9
|
+
id: SupportedAgent;
|
|
10
|
+
label: string;
|
|
11
|
+
/** legacy: the product was retired or renamed; setup still writes it, with a warning. */
|
|
12
|
+
status: "active" | "legacy";
|
|
13
|
+
installed: boolean;
|
|
14
|
+
/** A command, app or extension was found, not only a folder. A folder can outlive the
|
|
15
|
+
* program, or be one an earlier Summer setup made for its skills or config. */
|
|
16
|
+
programFound: boolean;
|
|
17
|
+
/** Every place that showed the agent is installed (empty when not installed). */
|
|
18
|
+
detectedBy: DetectHit[];
|
|
19
|
+
/** The MCP config file `summer setup <id>` writes at user scope (project scope for agents with no user file). */
|
|
20
|
+
configPath: string;
|
|
21
|
+
configScope: ConfigScope;
|
|
22
|
+
configFormat: McpFormat;
|
|
23
|
+
configExists: boolean;
|
|
24
|
+
/** The config file lists a summer-engine MCP server, in any shape. */
|
|
25
|
+
mcpEntry: boolean;
|
|
26
|
+
/** The command the summer-engine entry runs, when it is a local (stdio) entry. */
|
|
27
|
+
mcpServer: StdioMcpServerConfig | null;
|
|
28
|
+
/** Setup also adds the Summer Games MCP for this agent. */
|
|
29
|
+
summerGames: boolean;
|
|
30
|
+
/** Setup also installs skills for this agent. */
|
|
31
|
+
skills: boolean;
|
|
32
|
+
/** Set when the config file exists but cannot be read. */
|
|
33
|
+
configError?: SetupStepError;
|
|
34
|
+
}
|
|
35
|
+
export interface DetectOptions {
|
|
36
|
+
ctx?: PathContext;
|
|
37
|
+
/** Folders searched for macOS app bundles; default /Applications and ~/Applications. */
|
|
38
|
+
macAppDirs?: string[];
|
|
39
|
+
/** Shared command folders outside home searched on macOS and Linux; default Homebrew's /opt/homebrew/bin and /usr/local/bin. */
|
|
40
|
+
systemBinDirs?: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function detectAgents(options?: DetectOptions): DetectedAgent[];
|
|
43
|
+
/** PATH first, then the install folders a GUI-launched process's PATH leaves out. */
|
|
44
|
+
export declare function searchedBinDirs(ctx: PathContext, systemBinDirs?: string[]): string[];
|