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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -2
  3. package/dist/bin/summer.js +24 -1
  4. package/dist/commands/doctor.js +44 -20
  5. package/dist/commands/setup.js +77 -6
  6. package/dist/commands/skills.js +14 -6
  7. package/dist/lib/format.d.ts +23 -0
  8. package/dist/lib/format.js +60 -0
  9. package/dist/lib/format.test.d.ts +1 -0
  10. package/dist/lib/format.test.js +36 -0
  11. package/dist/lib/skills-registry.d.ts +33 -11
  12. package/dist/lib/skills-registry.js +71 -11
  13. package/package.json +1 -1
  14. package/skills/README.md +96 -0
  15. package/skills/_meta/skill-create/SKILL.md +143 -0
  16. package/skills/_meta/skill-improve/SKILL.md +89 -0
  17. package/skills/_meta/skill-test/SKILL.md +86 -0
  18. package/skills/_shared/collaborative-protocol.md +50 -0
  19. package/skills/_shared/gd-style.md +121 -0
  20. package/skills/_shared/godot-version.md +32 -0
  21. package/skills/_shared/mcp-tools-reference.md +133 -0
  22. package/skills/_shared/template-registry.md +40 -0
  23. package/skills/_tests/runner.md +90 -0
  24. package/skills/_tests/specs/3d-lighting.md +19 -0
  25. package/skills/_tests/specs/asset-strategy.md +19 -0
  26. package/skills/_tests/specs/debug.md +94 -0
  27. package/skills/_tests/specs/fps-controller.md +100 -0
  28. package/skills/_tests/specs/gdscript-patterns.md +19 -0
  29. package/skills/_tests/specs/make-game.md +19 -0
  30. package/skills/_tests/specs/play.md +88 -0
  31. package/skills/_tests/specs/scene-composition.md +19 -0
  32. package/skills/_tests/specs/ui-basics.md +19 -0
  33. package/skills/{asset-strategy → asset-pipeline/asset-strategy}/SKILL.md +5 -5
  34. package/skills/catalog.yaml +646 -0
  35. package/skills/{fps-controller → character-controllers/fps-controller}/SKILL.md +7 -5
  36. package/skills/debugging/debug/SKILL.md +137 -0
  37. package/skills/{3d-lighting → rendering-and-lighting/3d-lighting}/SKILL.md +6 -5
  38. package/skills/{make-game → scene-and-project/make-game}/SKILL.md +6 -5
  39. package/skills/scene-and-project/play/SKILL.md +108 -0
  40. package/skills/{scene-composition → scene-and-project/scene-composition}/SKILL.md +6 -5
  41. package/skills/{gdscript-patterns → scripting-patterns/gdscript-patterns}/SKILL.md +6 -5
  42. package/skills/{ui-basics → ui-and-ux/ui-basics}/SKILL.md +6 -5
  43. /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 verification prompt:
35
+ Then open your agent and try one of the slash commands:
36
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.
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
 
@@ -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
+ }
@@ -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("Run setup and connection diagnostics")
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
- const result = await runDoctor({ json: opts.json || !opts.human });
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: `summer-engine CLI v${version}`,
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: `Node ${process.version}`,
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: `Node ${process.version} found; Summer CLI requires Node 18 or newer.`,
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: "Not logged in. Run: summer login",
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 ? `Logged in as ${user.email}` : "Auth token found.",
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 Install",
102
+ label: "Engine",
94
103
  status: "ok",
95
- message: `Summer Engine binary found at ${binary}`,
104
+ message: tildeify(display),
96
105
  details: { path: binary },
97
106
  };
98
107
  }
99
108
  return {
100
109
  id: "engine-install",
101
- label: "Engine Install",
110
+ label: "Engine",
102
111
  status: "warning",
103
- message: "Summer Engine binary was not found in the default install locations.",
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: "Engine API token not found; Summer Engine is probably not running.",
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: `API token found, but Summer Engine is not responding on port ${port}.`,
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: `Summer Engine API is responding on port ${port}.`,
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: "MCP stdio server starts successfully.",
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("Summer Doctor\n");
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" ? "OK" : check.status === "warning" ? "WARN" : "FAIL";
231
- console.log(` ${mark.padEnd(4)} ${check.label}: ${check.message}`);
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;
@@ -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
- else if (config.dryRun) {
114
- console.log(`Would update ${config.path} for ${config.agent} (${config.scope}).`);
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(`Configured Summer Engine MCP for ${config.agent} (${config.scope}) at ${config.path}.`);
132
+ console.log(` ${sym.ok()} Linked to ${c.bold(agentLabel)} ${c.dim(tildeify(config.path))}`);
118
133
  }
119
134
  else {
120
- console.log(`Summer Engine MCP already configured for ${config.agent} (${config.scope}) at ${config.path}.`);
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.warn(`Warning: ${warning}`);
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
  }
@@ -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 path = join(skillsDir, name);
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 skillPath = join(skillsDir, name, "SKILL.md");
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 ${location.path}/<skill>/SKILL.md`);
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 ${location.path}/summer-<skill>.mdc`);
252
+ console.log(`Cursor rules are in ${tildeified}/summer-<skill>.mdc`);
245
253
  }
246
254
  else {
247
- console.log(`Windsurf rules are in ${location.path}`);
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 type SkillCategory = "workflow" | "scripting" | "scene" | "gameplay" | "lighting" | "assets" | "ui";
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 ["summer_set_prop", "summer_connect_signal", "summer_input_map_bind", "summer_get_script_errors"];
20
- readonly testScenario: "Create a typed GDScript with a signal, attach it to a node, connect the signal, and verify script errors.";
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"];