summer-engine 1.2.1 → 1.3.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/LICENSE +21 -0
- package/README.md +7 -2
- package/dist/bin/summer.js +24 -1
- package/dist/commands/doctor.js +44 -20
- package/dist/commands/setup.js +77 -6
- package/dist/commands/skills.js +14 -6
- package/dist/lib/format.d.ts +23 -0
- package/dist/lib/format.js +60 -0
- package/dist/lib/format.test.d.ts +1 -0
- package/dist/lib/format.test.js +36 -0
- package/dist/lib/skills-registry.d.ts +33 -11
- package/dist/lib/skills-registry.js +71 -11
- package/package.json +1 -1
- package/skills/README.md +96 -0
- package/skills/_meta/skill-create/SKILL.md +143 -0
- package/skills/_meta/skill-improve/SKILL.md +89 -0
- package/skills/_meta/skill-test/SKILL.md +86 -0
- package/skills/_shared/collaborative-protocol.md +50 -0
- package/skills/_shared/gd-style.md +121 -0
- package/skills/_shared/godot-version.md +32 -0
- package/skills/_shared/mcp-tools-reference.md +133 -0
- package/skills/_shared/template-registry.md +40 -0
- package/skills/_tests/runner.md +90 -0
- package/skills/_tests/specs/3d-lighting.md +19 -0
- package/skills/_tests/specs/asset-strategy.md +19 -0
- package/skills/_tests/specs/debug.md +94 -0
- package/skills/_tests/specs/fps-controller.md +100 -0
- package/skills/_tests/specs/gdscript-patterns.md +19 -0
- package/skills/_tests/specs/make-game.md +19 -0
- package/skills/_tests/specs/play.md +88 -0
- package/skills/_tests/specs/scene-composition.md +19 -0
- package/skills/_tests/specs/ui-basics.md +19 -0
- package/skills/{asset-strategy → asset-pipeline/asset-strategy}/SKILL.md +5 -5
- package/skills/catalog.yaml +646 -0
- package/skills/{fps-controller → character-controllers/fps-controller}/SKILL.md +7 -5
- package/skills/debugging/debug/SKILL.md +137 -0
- package/skills/{3d-lighting → rendering-and-lighting/3d-lighting}/SKILL.md +6 -5
- package/skills/{make-game → scene-and-project/make-game}/SKILL.md +6 -5
- package/skills/scene-and-project/play/SKILL.md +108 -0
- package/skills/{scene-composition → scene-and-project/scene-composition}/SKILL.md +6 -5
- package/skills/{gdscript-patterns → scripting-patterns/gdscript-patterns}/SKILL.md +6 -5
- package/skills/{ui-basics → ui-and-ux/ui-basics}/SKILL.md +6 -5
- /package/skills/{gdscript-patterns → scripting-patterns/gdscript-patterns}/reference.md +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Summer Engine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -32,9 +32,14 @@ Verify:
|
|
|
32
32
|
summer doctor
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Then open your agent and try the
|
|
35
|
+
Then open your agent and try one of the slash commands:
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
| Command | What it does |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `/debug` | Triage and fix a bug end-to-end (reads engine errors, locates code, proposes fix) |
|
|
40
|
+
| `/play` | Run the game and report state (clean run, warnings, or crash) |
|
|
41
|
+
|
|
42
|
+
Or just describe what you want — the specialist skills (FPS controller, lighting, GDScript patterns, scene composition, UI, asset strategy) auto-trigger on natural language: "make me an FPS", "add lighting", "I need a HUD".
|
|
38
43
|
|
|
39
44
|
## Commands
|
|
40
45
|
|
package/dist/bin/summer.js
CHANGED
|
@@ -14,13 +14,18 @@ import { skillsCommand } from "../commands/skills.js";
|
|
|
14
14
|
import { orchestratorCommand } from "../commands/orchestrator.js";
|
|
15
15
|
import { setupCommand } from "../commands/setup.js";
|
|
16
16
|
import { doctorCommand } from "../commands/doctor.js";
|
|
17
|
+
import { getBanner } from "../lib/banner.js";
|
|
18
|
+
import { c, sym } from "../lib/format.js";
|
|
17
19
|
const require = createRequire(import.meta.url);
|
|
18
20
|
const { version } = require("../../package.json");
|
|
19
21
|
const program = new Command();
|
|
20
22
|
program
|
|
21
23
|
.name("summer")
|
|
22
24
|
.description("CLI and MCP tools for Summer Engine — the AI-native game engine")
|
|
23
|
-
.version(version)
|
|
25
|
+
.version(version)
|
|
26
|
+
.action(() => {
|
|
27
|
+
printIntro(version);
|
|
28
|
+
});
|
|
24
29
|
program.addCommand(installCommand);
|
|
25
30
|
program.addCommand(loginCommand);
|
|
26
31
|
program.addCommand(logoutCommand);
|
|
@@ -38,3 +43,21 @@ program.parseAsync().catch((err) => {
|
|
|
38
43
|
console.error(err instanceof Error ? err.message : String(err));
|
|
39
44
|
process.exit(1);
|
|
40
45
|
});
|
|
46
|
+
function printIntro(version) {
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log(getBanner());
|
|
49
|
+
console.log("");
|
|
50
|
+
console.log(` ${sym.arrow()} ${c.bold("Summer Engine")} ${c.dim("v" + version)} ${c.dim("·")} AI-native game engine`);
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(` ${c.bold("Get started")}`);
|
|
53
|
+
console.log(` ${c.dim("$")} summer setup claude-code ${c.dim("Configure Claude Code (or codex/cursor/windsurf)")}`);
|
|
54
|
+
console.log(` ${c.dim("$")} summer create 3d-basic my-game ${c.dim("Start a project")}`);
|
|
55
|
+
console.log(` ${c.dim("$")} summer doctor ${c.dim("Check setup")}`);
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(` ${c.bold("Slash commands")} ${c.dim("(in your AI agent)")}`);
|
|
58
|
+
console.log(` ${c.brand("/debug")} ${c.dim("Triage and fix a bug end-to-end")}`);
|
|
59
|
+
console.log(` ${c.brand("/play")} ${c.dim("Run the game and report state")}`);
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(` ${c.dim("Docs:")} https://summerengine.com/docs`);
|
|
62
|
+
console.log("");
|
|
63
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { platform } from "os";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { getAuthToken, getUserInfo } from "../lib/auth.js";
|
|
7
7
|
import { checkEngineHealth, getApiPort, getApiToken } from "../lib/engine.js";
|
|
8
|
+
import { brandLine, c, pad, sym, tildeify } from "../lib/format.js";
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
const { version } = require("../../package.json");
|
|
10
11
|
const MAC_ENGINE_PATHS = [
|
|
@@ -17,11 +18,17 @@ const WIN_ENGINE_PATHS = [
|
|
|
17
18
|
`${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
|
|
18
19
|
];
|
|
19
20
|
export const doctorCommand = new Command("doctor")
|
|
20
|
-
.description("
|
|
21
|
+
.description("Diagnose Node, login, engine, local API, and MCP boot")
|
|
21
22
|
.option("--json", "Print diagnostics as JSON")
|
|
22
|
-
.option("--human", "Print diagnostics in a human-readable format")
|
|
23
23
|
.action(async (opts) => {
|
|
24
|
-
|
|
24
|
+
if (!opts.json) {
|
|
25
|
+
const { createRequire: req } = await import("node:module");
|
|
26
|
+
const ver = req(import.meta.url)("../../package.json").version;
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(brandLine(ver));
|
|
29
|
+
console.log("");
|
|
30
|
+
}
|
|
31
|
+
const result = await runDoctor({ json: Boolean(opts.json) });
|
|
25
32
|
if (!result.ok) {
|
|
26
33
|
process.exit(1);
|
|
27
34
|
}
|
|
@@ -33,7 +40,7 @@ export async function runDoctor(options = {}) {
|
|
|
33
40
|
id: "cli-version",
|
|
34
41
|
label: "Summer CLI",
|
|
35
42
|
status: "ok",
|
|
36
|
-
message: `
|
|
43
|
+
message: `v${version}`,
|
|
37
44
|
});
|
|
38
45
|
checks.push(await checkLogin());
|
|
39
46
|
checks.push(checkEngineInstall());
|
|
@@ -57,14 +64,14 @@ function checkNodeVersion() {
|
|
|
57
64
|
id: "node-version",
|
|
58
65
|
label: "Node.js",
|
|
59
66
|
status: "ok",
|
|
60
|
-
message:
|
|
67
|
+
message: process.version,
|
|
61
68
|
};
|
|
62
69
|
}
|
|
63
70
|
return {
|
|
64
71
|
id: "node-version",
|
|
65
72
|
label: "Node.js",
|
|
66
73
|
status: "fail",
|
|
67
|
-
message:
|
|
74
|
+
message: `${process.version} (need 18+)`,
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
async function checkLogin() {
|
|
@@ -75,32 +82,34 @@ async function checkLogin() {
|
|
|
75
82
|
id: "login",
|
|
76
83
|
label: "Login",
|
|
77
84
|
status: "warning",
|
|
78
|
-
message: "
|
|
85
|
+
message: "not signed in (run: summer login)",
|
|
79
86
|
};
|
|
80
87
|
}
|
|
81
88
|
return {
|
|
82
89
|
id: "login",
|
|
83
90
|
label: "Login",
|
|
84
91
|
status: "ok",
|
|
85
|
-
message: user ?
|
|
92
|
+
message: user ? user.email : "signed in",
|
|
86
93
|
};
|
|
87
94
|
}
|
|
88
95
|
function checkEngineInstall() {
|
|
89
96
|
const binary = findEngineBinary();
|
|
90
97
|
if (binary) {
|
|
98
|
+
// Shorten /Applications/Summer.app/Contents/MacOS/Summer -> /Applications/Summer.app
|
|
99
|
+
const display = binary.replace(/\/Contents\/MacOS\/Summer$/, "");
|
|
91
100
|
return {
|
|
92
101
|
id: "engine-install",
|
|
93
|
-
label: "Engine
|
|
102
|
+
label: "Engine",
|
|
94
103
|
status: "ok",
|
|
95
|
-
message:
|
|
104
|
+
message: tildeify(display),
|
|
96
105
|
details: { path: binary },
|
|
97
106
|
};
|
|
98
107
|
}
|
|
99
108
|
return {
|
|
100
109
|
id: "engine-install",
|
|
101
|
-
label: "Engine
|
|
110
|
+
label: "Engine",
|
|
102
111
|
status: "warning",
|
|
103
|
-
message: "
|
|
112
|
+
message: "not installed (run: summer install)",
|
|
104
113
|
};
|
|
105
114
|
}
|
|
106
115
|
async function checkLocalApi() {
|
|
@@ -111,7 +120,7 @@ async function checkLocalApi() {
|
|
|
111
120
|
id: "local-api",
|
|
112
121
|
label: "Local API",
|
|
113
122
|
status: "warning",
|
|
114
|
-
message: "
|
|
123
|
+
message: "engine not running",
|
|
115
124
|
details: { port },
|
|
116
125
|
};
|
|
117
126
|
}
|
|
@@ -121,7 +130,7 @@ async function checkLocalApi() {
|
|
|
121
130
|
id: "local-api",
|
|
122
131
|
label: "Local API",
|
|
123
132
|
status: "warning",
|
|
124
|
-
message: `
|
|
133
|
+
message: `not responding on :${port}`,
|
|
125
134
|
details: { port },
|
|
126
135
|
};
|
|
127
136
|
}
|
|
@@ -129,7 +138,7 @@ async function checkLocalApi() {
|
|
|
129
138
|
id: "local-api",
|
|
130
139
|
label: "Local API",
|
|
131
140
|
status: "ok",
|
|
132
|
-
message:
|
|
141
|
+
message: `:${port}`,
|
|
133
142
|
details: {
|
|
134
143
|
port,
|
|
135
144
|
version: health.version,
|
|
@@ -182,7 +191,7 @@ async function checkMcpBoot() {
|
|
|
182
191
|
id: "mcp-boot",
|
|
183
192
|
label: "MCP Server",
|
|
184
193
|
status: "ok",
|
|
185
|
-
message: "
|
|
194
|
+
message: "ready",
|
|
186
195
|
});
|
|
187
196
|
}
|
|
188
197
|
});
|
|
@@ -225,12 +234,27 @@ function summarizeChecks(checks) {
|
|
|
225
234
|
};
|
|
226
235
|
}
|
|
227
236
|
export function printDoctorResult(result) {
|
|
228
|
-
console.log("
|
|
237
|
+
console.log(c.bold("Doctor"));
|
|
238
|
+
console.log("");
|
|
239
|
+
const labelWidth = 14;
|
|
229
240
|
for (const check of result.checks) {
|
|
230
|
-
const mark = check.status === "ok"
|
|
231
|
-
|
|
241
|
+
const mark = check.status === "ok"
|
|
242
|
+
? sym.ok()
|
|
243
|
+
: check.status === "warning"
|
|
244
|
+
? sym.warn()
|
|
245
|
+
: sym.fail();
|
|
246
|
+
console.log(` ${mark} ${pad(c.bold(check.label), labelWidth)}${c.dim(check.message)}`);
|
|
247
|
+
}
|
|
248
|
+
console.log("");
|
|
249
|
+
if (result.summary.failures > 0) {
|
|
250
|
+
console.log(c.red(`${result.summary.failures} ${result.summary.failures === 1 ? "issue" : "issues"} to fix.`));
|
|
251
|
+
}
|
|
252
|
+
else if (result.summary.warnings > 0) {
|
|
253
|
+
console.log(c.yellow(`${result.summary.warnings} ${result.summary.warnings === 1 ? "warning" : "warnings"}, ${result.summary.ok} OK.`));
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
console.log(c.green("Everything's wired up."));
|
|
232
257
|
}
|
|
233
|
-
console.log(`\n${result.summary.ok} ok, ${result.summary.warnings} warnings, ${result.summary.failures} failures`);
|
|
234
258
|
}
|
|
235
259
|
function findEngineBinary() {
|
|
236
260
|
const paths = platform() === "darwin" ? MAC_ENGINE_PATHS : WIN_ENGINE_PATHS;
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { spawnSync } from "child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import { configureAgentMcp, parseAgent, parseScope, supportedAgents, } from "../lib/agent-config.js";
|
|
4
5
|
import { printDoctorResult, runDoctor } from "./doctor.js";
|
|
6
|
+
import { brandLine, c, sym, tildeify } from "../lib/format.js";
|
|
7
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
8
|
+
const { version: cliVersion } = requireFromHere("../../package.json");
|
|
9
|
+
const AGENT_LABEL = {
|
|
10
|
+
"claude-code": "Claude Code",
|
|
11
|
+
codex: "Codex",
|
|
12
|
+
cursor: "Cursor",
|
|
13
|
+
windsurf: "Windsurf",
|
|
14
|
+
};
|
|
5
15
|
export const setupCommand = new Command("setup")
|
|
6
16
|
.description("Configure Summer Engine for an AI agent and run diagnostics")
|
|
7
17
|
.argument("[agent]", `Agent to configure: ${supportedAgents.join(", ")}`)
|
|
@@ -109,20 +119,81 @@ function skillInstallInvocation(agent) {
|
|
|
109
119
|
function printSetupResult(config, skills, doctor) {
|
|
110
120
|
if (config.print) {
|
|
111
121
|
console.log(config.snippet.trimEnd());
|
|
122
|
+
return;
|
|
112
123
|
}
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(brandLine(cliVersion));
|
|
126
|
+
console.log("");
|
|
127
|
+
const agentLabel = AGENT_LABEL[config.agent] ?? config.agent;
|
|
128
|
+
if (config.dryRun) {
|
|
129
|
+
console.log(` ${c.dim("(dry run)")} Would link to ${agentLabel} ${c.dim(tildeify(config.path))}`);
|
|
115
130
|
}
|
|
116
131
|
else if (config.wrote) {
|
|
117
|
-
console.log(`
|
|
132
|
+
console.log(` ${sym.ok()} Linked to ${c.bold(agentLabel)} ${c.dim(tildeify(config.path))}`);
|
|
118
133
|
}
|
|
119
134
|
else {
|
|
120
|
-
console.log(`
|
|
135
|
+
console.log(` ${sym.ok()} Already linked to ${c.bold(agentLabel)} ${c.dim(tildeify(config.path))}`);
|
|
136
|
+
}
|
|
137
|
+
if (skills.status === "installed") {
|
|
138
|
+
const installed = parseInstalledSkills(skills.stdout);
|
|
139
|
+
if (installed.length > 0) {
|
|
140
|
+
const where = parseSkillTargetDir(skills.stdout);
|
|
141
|
+
console.log(` ${sym.ok()} Installed ${c.bold(String(installed.length) + " skills")} ${where ? c.dim(tildeify(where)) : ""}`);
|
|
142
|
+
const grouped = installed.reduce((rows, name, i) => {
|
|
143
|
+
const row = Math.floor(i / 3);
|
|
144
|
+
rows[row] = rows[row] ?? [];
|
|
145
|
+
rows[row].push(name);
|
|
146
|
+
return rows;
|
|
147
|
+
}, []);
|
|
148
|
+
for (const row of grouped) {
|
|
149
|
+
console.log(` ${c.dim(row.join(" · "))}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(` ${sym.ok()} Installed recommended skills`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (skills.status === "planned" || skills.status === "skipped") {
|
|
157
|
+
console.log(` ${sym.warn()} Skills: ${c.dim(skills.message)}`);
|
|
158
|
+
}
|
|
159
|
+
else if (skills.status === "failed") {
|
|
160
|
+
console.log(` ${sym.fail()} Skills install failed`);
|
|
161
|
+
if (skills.stderr)
|
|
162
|
+
console.log(` ${c.dim(skills.stderr)}`);
|
|
121
163
|
}
|
|
122
164
|
for (const warning of config.warnings) {
|
|
123
|
-
console.
|
|
165
|
+
console.log(` ${sym.warn()} ${c.yellow(warning)}`);
|
|
124
166
|
}
|
|
125
|
-
console.log(`Skills: ${skills.message}`);
|
|
126
167
|
console.log("");
|
|
127
168
|
printDoctorResult(doctor);
|
|
169
|
+
if (doctor.ok && skills.status !== "failed" && !config.dryRun) {
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log(`${c.dim("Try it:")} open ${AGENT_LABEL[config.agent] ?? config.agent} and ask: ${c.bold("\"add a DirectionalLight3D and Camera3D to my scene\"")}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function parseInstalledSkills(stdout) {
|
|
175
|
+
if (!stdout)
|
|
176
|
+
return [];
|
|
177
|
+
// Lines look like: " Installed fps-controller -> /path/.../fps-controller"
|
|
178
|
+
const names = [];
|
|
179
|
+
for (const line of stdout.split("\n")) {
|
|
180
|
+
const m = line.match(/^\s*Installed\s+([a-z0-9-]+)\s+->/i);
|
|
181
|
+
if (m)
|
|
182
|
+
names.push(m[1]);
|
|
183
|
+
}
|
|
184
|
+
return names;
|
|
185
|
+
}
|
|
186
|
+
function parseSkillTargetDir(stdout) {
|
|
187
|
+
if (!stdout)
|
|
188
|
+
return null;
|
|
189
|
+
for (const line of stdout.split("\n")) {
|
|
190
|
+
const m = line.match(/^\s*Installed\s+[a-z0-9-]+\s+->\s+(.+)$/i);
|
|
191
|
+
if (m) {
|
|
192
|
+
const path = m[1].trim();
|
|
193
|
+
// Strip trailing /<skill-name> to get the parent dir
|
|
194
|
+
const parent = path.replace(/\/[a-z0-9-]+$/i, "/");
|
|
195
|
+
return parent;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
128
199
|
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, dirname } from "path";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { AGENT_CLIENTS, SKILL_REGISTRY, } from "../lib/skills-registry.js";
|
|
7
|
+
import { tildeify } from "../lib/format.js";
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
// Resolve skills dir: from dist/commands/skills.js -> ../../skills
|
|
9
10
|
const skillsDir = join(__dirname, "..", "..", "skills");
|
|
@@ -13,7 +14,7 @@ function getBuiltinSkills() {
|
|
|
13
14
|
for (const entry of SKILL_REGISTRY) {
|
|
14
15
|
if (!entry.public)
|
|
15
16
|
continue;
|
|
16
|
-
const skillPath = join(skillsDir, entry.name, "SKILL.md");
|
|
17
|
+
const skillPath = join(skillsDir, entry.category, entry.name, "SKILL.md");
|
|
17
18
|
if (!existsSync(skillPath))
|
|
18
19
|
continue;
|
|
19
20
|
const frontmatter = parseSkillFrontmatter(skillPath);
|
|
@@ -48,13 +49,19 @@ function getSkillMeta(name) {
|
|
|
48
49
|
return getBuiltinSkills().find((skill) => skill.name === name) ?? null;
|
|
49
50
|
}
|
|
50
51
|
function getSkillPath(name) {
|
|
51
|
-
const
|
|
52
|
+
const entry = SKILL_REGISTRY.find((s) => s.name === name);
|
|
53
|
+
if (!entry)
|
|
54
|
+
return null;
|
|
55
|
+
const path = join(skillsDir, entry.category, entry.name);
|
|
52
56
|
if (!existsSync(path) || !existsSync(join(path, "SKILL.md")))
|
|
53
57
|
return null;
|
|
54
58
|
return path;
|
|
55
59
|
}
|
|
56
60
|
function getSkillBody(name) {
|
|
57
|
-
const
|
|
61
|
+
const entry = SKILL_REGISTRY.find((s) => s.name === name);
|
|
62
|
+
if (!entry)
|
|
63
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
64
|
+
const skillPath = join(skillsDir, entry.category, entry.name, "SKILL.md");
|
|
58
65
|
const content = readFileSync(skillPath, "utf-8");
|
|
59
66
|
return content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "").trim();
|
|
60
67
|
}
|
|
@@ -236,15 +243,16 @@ function escapeRegExp(value) {
|
|
|
236
243
|
}
|
|
237
244
|
function printInstallSummary(count, agent, scope, location) {
|
|
238
245
|
const label = agentLabel(agent);
|
|
246
|
+
const tildeified = tildeify(location.path);
|
|
239
247
|
console.log(`\n${count} skill${count === 1 ? "" : "s"} ready for ${label} (${scope} scope).`);
|
|
240
248
|
if (location.kind === "skill-dir") {
|
|
241
|
-
console.log(`${label} can read skills from ${
|
|
249
|
+
console.log(`${label} can read skills from ${tildeified}/<skill>/SKILL.md`);
|
|
242
250
|
}
|
|
243
251
|
else if (location.kind === "cursor-rule-dir") {
|
|
244
|
-
console.log(`Cursor rules are in ${
|
|
252
|
+
console.log(`Cursor rules are in ${tildeified}/summer-<skill>.mdc`);
|
|
245
253
|
}
|
|
246
254
|
else {
|
|
247
|
-
console.log(`Windsurf rules are in ${
|
|
255
|
+
console.log(`Windsurf rules are in ${tildeified}`);
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
export const skillsCommand = new Command("skills")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function useColor(): boolean;
|
|
2
|
+
export declare const c: {
|
|
3
|
+
bold: (s: string) => string;
|
|
4
|
+
dim: (s: string) => string;
|
|
5
|
+
red: (s: string) => string;
|
|
6
|
+
green: (s: string) => string;
|
|
7
|
+
yellow: (s: string) => string;
|
|
8
|
+
cyan: (s: string) => string;
|
|
9
|
+
brand: (s: string) => string;
|
|
10
|
+
};
|
|
11
|
+
export declare const sym: {
|
|
12
|
+
ok: () => string;
|
|
13
|
+
warn: () => string;
|
|
14
|
+
fail: () => string;
|
|
15
|
+
arrow: () => string;
|
|
16
|
+
};
|
|
17
|
+
export declare function tildeify(path: string | undefined | null): string;
|
|
18
|
+
export declare function brandLine(version: string): string;
|
|
19
|
+
export declare function stripAnsi(s: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Pad a string to a visible width, accounting for ANSI codes.
|
|
22
|
+
*/
|
|
23
|
+
export declare function pad(s: string, width: number): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
const ANSI = {
|
|
3
|
+
reset: "\x1b[0m",
|
|
4
|
+
bold: "\x1b[1m",
|
|
5
|
+
dim: "\x1b[2m",
|
|
6
|
+
red: "\x1b[31m",
|
|
7
|
+
green: "\x1b[32m",
|
|
8
|
+
yellow: "\x1b[33m",
|
|
9
|
+
cyan: "\x1b[36m",
|
|
10
|
+
// Summer brand orange (matches banner.ts mid gradient)
|
|
11
|
+
brand: "\x1b[38;2;255;143;23m",
|
|
12
|
+
};
|
|
13
|
+
export function useColor() {
|
|
14
|
+
if (process.env.NO_COLOR)
|
|
15
|
+
return false;
|
|
16
|
+
if (process.env.FORCE_COLOR)
|
|
17
|
+
return true;
|
|
18
|
+
return Boolean(process.stdout.isTTY);
|
|
19
|
+
}
|
|
20
|
+
function paint(code, s) {
|
|
21
|
+
return useColor() ? `${code}${s}${ANSI.reset}` : s;
|
|
22
|
+
}
|
|
23
|
+
export const c = {
|
|
24
|
+
bold: (s) => paint(ANSI.bold, s),
|
|
25
|
+
dim: (s) => paint(ANSI.dim, s),
|
|
26
|
+
red: (s) => paint(ANSI.red, s),
|
|
27
|
+
green: (s) => paint(ANSI.green, s),
|
|
28
|
+
yellow: (s) => paint(ANSI.yellow, s),
|
|
29
|
+
cyan: (s) => paint(ANSI.cyan, s),
|
|
30
|
+
brand: (s) => paint(ANSI.brand, s),
|
|
31
|
+
};
|
|
32
|
+
export const sym = {
|
|
33
|
+
ok: () => c.green("✓"),
|
|
34
|
+
warn: () => c.yellow("⚠"),
|
|
35
|
+
fail: () => c.red("✗"),
|
|
36
|
+
arrow: () => c.brand("▶"),
|
|
37
|
+
};
|
|
38
|
+
export function tildeify(path) {
|
|
39
|
+
if (!path)
|
|
40
|
+
return "";
|
|
41
|
+
const home = homedir();
|
|
42
|
+
if (path === home)
|
|
43
|
+
return "~";
|
|
44
|
+
if (path.startsWith(home + "/"))
|
|
45
|
+
return "~" + path.slice(home.length);
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
export function brandLine(version) {
|
|
49
|
+
return `${sym.arrow()} ${c.bold("Summer Engine")} ${c.dim("v" + version)}`;
|
|
50
|
+
}
|
|
51
|
+
export function stripAnsi(s) {
|
|
52
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pad a string to a visible width, accounting for ANSI codes.
|
|
56
|
+
*/
|
|
57
|
+
export function pad(s, width) {
|
|
58
|
+
const visLen = stripAnsi(s).length;
|
|
59
|
+
return s + " ".repeat(Math.max(0, width - visLen));
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { stripAnsi, tildeify } from "./format.js";
|
|
4
|
+
describe("tildeify", () => {
|
|
5
|
+
it("replaces home directory prefix with ~", () => {
|
|
6
|
+
const home = homedir();
|
|
7
|
+
expect(tildeify(home + "/.claude.json")).toBe("~/.claude.json");
|
|
8
|
+
expect(tildeify(home + "/projects/foo")).toBe("~/projects/foo");
|
|
9
|
+
});
|
|
10
|
+
it("returns ~ when path is the home directory exactly", () => {
|
|
11
|
+
expect(tildeify(homedir())).toBe("~");
|
|
12
|
+
});
|
|
13
|
+
it("does not touch non-home paths", () => {
|
|
14
|
+
expect(tildeify("/Applications/Summer.app")).toBe("/Applications/Summer.app");
|
|
15
|
+
expect(tildeify("/tmp/foo")).toBe("/tmp/foo");
|
|
16
|
+
});
|
|
17
|
+
it("does not partial-match (e.g. /Users/Mat vs /Users/MathiasWork)", () => {
|
|
18
|
+
const home = homedir();
|
|
19
|
+
const sibling = home.slice(0, -2) + "Other";
|
|
20
|
+
expect(tildeify(sibling + "/file")).toBe(sibling + "/file");
|
|
21
|
+
});
|
|
22
|
+
it("handles empty/null/undefined input", () => {
|
|
23
|
+
expect(tildeify(undefined)).toBe("");
|
|
24
|
+
expect(tildeify(null)).toBe("");
|
|
25
|
+
expect(tildeify("")).toBe("");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("stripAnsi", () => {
|
|
29
|
+
it("removes ANSI color codes", () => {
|
|
30
|
+
expect(stripAnsi("\x1b[31mhello\x1b[0m")).toBe("hello");
|
|
31
|
+
expect(stripAnsi("\x1b[1;32mbold green\x1b[0m text")).toBe("bold green text");
|
|
32
|
+
});
|
|
33
|
+
it("leaves clean strings untouched", () => {
|
|
34
|
+
expect(stripAnsi("hello")).toBe("hello");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -1,67 +1,89 @@
|
|
|
1
1
|
export declare const AGENT_CLIENTS: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
2
2
|
export type AgentClient = (typeof AGENT_CLIENTS)[number];
|
|
3
|
-
export
|
|
3
|
+
export declare const SKILL_CATEGORIES: readonly ["_meta", "character-controllers", "gameplay-mechanics", "scripting-patterns", "scene-and-project", "rendering-and-lighting", "shaders", "visual-effects", "post-processing", "animation", "audio", "physics", "multiplayer-and-networking", "ai-and-npcs", "level-design", "performance", "ui-and-ux", "asset-pipeline", "input-and-controls", "debugging", "deployment"];
|
|
4
|
+
export type SkillCategory = (typeof SKILL_CATEGORIES)[number];
|
|
4
5
|
export interface SkillRegistryEntry {
|
|
5
6
|
name: string;
|
|
6
|
-
public: boolean;
|
|
7
7
|
category: SkillCategory;
|
|
8
|
+
public: boolean;
|
|
8
9
|
clients: readonly AgentClient[];
|
|
9
10
|
recommended: boolean;
|
|
10
11
|
requiresMcpTools: readonly string[];
|
|
11
12
|
testScenario: string;
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Skills with `status: HAVE` in catalog.yaml. The catalog is the planning source
|
|
16
|
+
* of truth (~85 skills); this TS registry is the install-time list (only skills
|
|
17
|
+
* with shippable content). When a NEXT skill ships, mirror it here.
|
|
18
|
+
*/
|
|
13
19
|
export declare const SKILL_REGISTRY: readonly [{
|
|
20
|
+
readonly name: "debug";
|
|
21
|
+
readonly category: "debugging";
|
|
22
|
+
readonly public: true;
|
|
23
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
24
|
+
readonly recommended: true;
|
|
25
|
+
readonly requiresMcpTools: readonly ["summer_get_console", "summer_get_debugger_errors", "summer_get_script_errors", "summer_get_diagnostics", "summer_clear_console", "summer_play", "summer_stop", "summer_inspect_node"];
|
|
26
|
+
readonly testScenario: "Triage a runtime crash end-to-end: read errors, locate code, propose fix, apply on approval, verify.";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "play";
|
|
29
|
+
readonly category: "scene-and-project";
|
|
30
|
+
readonly public: true;
|
|
31
|
+
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
32
|
+
readonly recommended: true;
|
|
33
|
+
readonly requiresMcpTools: readonly ["summer_is_running", "summer_play", "summer_stop", "summer_clear_console", "summer_get_diagnostics", "summer_get_debugger_errors", "summer_get_script_errors"];
|
|
34
|
+
readonly testScenario: "Run the game from a clean state, wait for first frame, report whether it runs cleanly or has errors.";
|
|
35
|
+
}, {
|
|
14
36
|
readonly name: "gdscript-patterns";
|
|
37
|
+
readonly category: "scripting-patterns";
|
|
15
38
|
readonly public: true;
|
|
16
|
-
readonly category: "scripting";
|
|
17
39
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
18
40
|
readonly recommended: true;
|
|
19
|
-
readonly requiresMcpTools: readonly ["
|
|
20
|
-
readonly testScenario: "
|
|
41
|
+
readonly requiresMcpTools: readonly ["summer_get_script_errors"];
|
|
42
|
+
readonly testScenario: "Write a typed GDScript with a signal, attach it to a node, connect the signal, and verify script errors.";
|
|
21
43
|
}, {
|
|
22
44
|
readonly name: "scene-composition";
|
|
45
|
+
readonly category: "scene-and-project";
|
|
23
46
|
readonly public: true;
|
|
24
|
-
readonly category: "scene";
|
|
25
47
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
26
48
|
readonly recommended: true;
|
|
27
49
|
readonly requiresMcpTools: readonly ["summer_create_scene", "summer_open_scene", "summer_get_scene_tree", "summer_add_node", "summer_instantiate_scene", "summer_save_scene"];
|
|
28
50
|
readonly testScenario: "Build a small reusable player scene, instantiate it in a main scene, and save both scenes through Summer tools.";
|
|
29
51
|
}, {
|
|
30
52
|
readonly name: "fps-controller";
|
|
53
|
+
readonly category: "character-controllers";
|
|
31
54
|
readonly public: true;
|
|
32
|
-
readonly category: "gameplay";
|
|
33
55
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
34
56
|
readonly recommended: true;
|
|
35
57
|
readonly requiresMcpTools: readonly ["summer_add_node", "summer_set_prop", "summer_set_resource_property", "summer_input_map_bind"];
|
|
36
58
|
readonly testScenario: "Create a CharacterBody3D player with collision, camera, input actions, and a host-edited controller script.";
|
|
37
59
|
}, {
|
|
38
60
|
readonly name: "3d-lighting";
|
|
61
|
+
readonly category: "rendering-and-lighting";
|
|
39
62
|
readonly public: true;
|
|
40
|
-
readonly category: "lighting";
|
|
41
63
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
42
64
|
readonly recommended: true;
|
|
43
65
|
readonly requiresMcpTools: readonly ["summer_add_node", "summer_set_prop", "summer_set_resource_property"];
|
|
44
66
|
readonly testScenario: "Add sun, fill lighting, WorldEnvironment, and shadow settings to a simple 3D scene.";
|
|
45
67
|
}, {
|
|
46
68
|
readonly name: "ui-basics";
|
|
69
|
+
readonly category: "ui-and-ux";
|
|
47
70
|
readonly public: true;
|
|
48
|
-
readonly category: "ui";
|
|
49
71
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
50
72
|
readonly recommended: true;
|
|
51
73
|
readonly requiresMcpTools: readonly ["summer_add_node", "summer_set_prop", "summer_connect_signal"];
|
|
52
74
|
readonly testScenario: "Create a HUD CanvasLayer with labels, a ProgressBar, and a connected Button signal.";
|
|
53
75
|
}, {
|
|
54
76
|
readonly name: "asset-strategy";
|
|
77
|
+
readonly category: "asset-pipeline";
|
|
55
78
|
readonly public: true;
|
|
56
|
-
readonly category: "assets";
|
|
57
79
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
58
80
|
readonly recommended: true;
|
|
59
81
|
readonly requiresMcpTools: readonly ["summer_search_assets", "summer_import_asset", "summer_import_from_url", "summer_generate_image", "summer_generate_3d", "summer_generate_audio"];
|
|
60
82
|
readonly testScenario: "Choose library, generated, or primitive assets for a small level and import one asset into the project.";
|
|
61
83
|
}, {
|
|
62
84
|
readonly name: "make-game";
|
|
85
|
+
readonly category: "scene-and-project";
|
|
63
86
|
readonly public: true;
|
|
64
|
-
readonly category: "workflow";
|
|
65
87
|
readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
|
|
66
88
|
readonly recommended: false;
|
|
67
89
|
readonly requiresMcpTools: readonly ["summer_get_agent_playbook", "summer_get_project_context", "summer_create_scene", "summer_open_scene", "summer_save_scene", "summer_add_node", "summer_set_prop", "summer_set_resource_property", "summer_instantiate_scene", "summer_play", "summer_stop", "summer_get_diagnostics"];
|