summer-engine 1.2.0 → 1.3.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -24
  3. package/dist/bin/summer.js +4 -0
  4. package/dist/commands/doctor.d.ts +26 -0
  5. package/dist/commands/doctor.js +248 -0
  6. package/dist/commands/doctor.test.d.ts +1 -0
  7. package/dist/commands/doctor.test.js +29 -0
  8. package/dist/commands/mcp.js +49 -0
  9. package/dist/commands/setup.d.ts +2 -0
  10. package/dist/commands/setup.js +128 -0
  11. package/dist/commands/skills.js +254 -109
  12. package/dist/lib/agent-config.d.ts +39 -0
  13. package/dist/lib/agent-config.js +284 -0
  14. package/dist/lib/agent-config.test.d.ts +1 -0
  15. package/dist/lib/agent-config.test.js +80 -0
  16. package/dist/lib/api-client.d.ts +19 -2
  17. package/dist/lib/api-client.js +129 -14
  18. package/dist/lib/skills-registry.d.ts +75 -0
  19. package/dist/lib/skills-registry.js +143 -0
  20. package/dist/mcp/tools/project-tools.js +100 -23
  21. package/dist/mcp/tools/with-engine.js +3 -53
  22. package/package.json +5 -2
  23. package/skills/README.md +96 -0
  24. package/skills/_meta/skill-create/SKILL.md +143 -0
  25. package/skills/_meta/skill-improve/SKILL.md +89 -0
  26. package/skills/_meta/skill-test/SKILL.md +86 -0
  27. package/skills/_shared/collaborative-protocol.md +50 -0
  28. package/skills/_shared/gd-style.md +121 -0
  29. package/skills/_shared/godot-version.md +32 -0
  30. package/skills/_shared/mcp-tools-reference.md +133 -0
  31. package/skills/_shared/template-registry.md +40 -0
  32. package/skills/_tests/runner.md +90 -0
  33. package/skills/_tests/specs/3d-lighting.md +19 -0
  34. package/skills/_tests/specs/asset-strategy.md +19 -0
  35. package/skills/_tests/specs/fps-controller.md +100 -0
  36. package/skills/_tests/specs/gdscript-patterns.md +19 -0
  37. package/skills/_tests/specs/make-game.md +19 -0
  38. package/skills/_tests/specs/scene-composition.md +19 -0
  39. package/skills/_tests/specs/ui-basics.md +19 -0
  40. package/skills/{asset-strategy → asset-pipeline/asset-strategy}/SKILL.md +4 -5
  41. package/skills/catalog.yaml +636 -0
  42. package/skills/{fps-controller → character-controllers/fps-controller}/SKILL.md +6 -5
  43. package/skills/{3d-lighting → rendering-and-lighting/3d-lighting}/SKILL.md +5 -5
  44. package/skills/{make-game → scene-and-project/make-game}/SKILL.md +8 -8
  45. package/skills/{scene-composition → scene-and-project/scene-composition}/SKILL.md +6 -6
  46. package/skills/{gdscript-patterns → scripting-patterns/gdscript-patterns}/SKILL.md +7 -7
  47. package/skills/{ui-basics → ui-and-ux/ui-basics}/SKILL.md +5 -5
  48. /package/skills/{gdscript-patterns → scripting-patterns/gdscript-patterns}/reference.md +0 -0
@@ -1,25 +1,29 @@
1
1
  import { Command } from "commander";
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, cpSync, } from "fs";
2
+ import { existsSync, mkdirSync, readFileSync, cpSync, writeFileSync, } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { homedir } from "os";
6
+ import { AGENT_CLIENTS, SKILL_REGISTRY, } from "../lib/skills-registry.js";
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
  // Resolve skills dir: from dist/commands/skills.js -> ../../skills
8
9
  const skillsDir = join(__dirname, "..", "..", "skills");
10
+ const SKILL_SCOPES = ["user", "project"];
9
11
  function getBuiltinSkills() {
10
- if (!existsSync(skillsDir))
11
- return [];
12
- const entries = readdirSync(skillsDir, { withFileTypes: true });
13
12
  const skills = [];
14
- for (const entry of entries) {
15
- if (!entry.isDirectory())
13
+ for (const entry of SKILL_REGISTRY) {
14
+ if (!entry.public)
16
15
  continue;
17
- const skillPath = join(skillsDir, entry.name, "SKILL.md");
16
+ const skillPath = join(skillsDir, entry.category, entry.name, "SKILL.md");
18
17
  if (!existsSync(skillPath))
19
18
  continue;
20
- const meta = parseSkillFrontmatter(skillPath);
21
- if (meta)
22
- skills.push(meta);
19
+ const frontmatter = parseSkillFrontmatter(skillPath);
20
+ if (!frontmatter)
21
+ continue;
22
+ skills.push({
23
+ ...entry,
24
+ name: entry.name,
25
+ description: frontmatter.description,
26
+ });
23
27
  }
24
28
  return skills;
25
29
  }
@@ -40,12 +44,215 @@ function parseSkillFrontmatter(skillPath) {
40
44
  return null;
41
45
  }
42
46
  }
47
+ function getSkillMeta(name) {
48
+ return getBuiltinSkills().find((skill) => skill.name === name) ?? null;
49
+ }
43
50
  function getSkillPath(name) {
44
- const path = join(skillsDir, name);
51
+ const entry = SKILL_REGISTRY.find((s) => s.name === name);
52
+ if (!entry)
53
+ return null;
54
+ const path = join(skillsDir, entry.category, entry.name);
45
55
  if (!existsSync(path) || !existsSync(join(path, "SKILL.md")))
46
56
  return null;
47
57
  return path;
48
58
  }
59
+ function getSkillBody(name) {
60
+ const entry = SKILL_REGISTRY.find((s) => s.name === name);
61
+ if (!entry)
62
+ throw new Error(`Unknown skill: ${name}`);
63
+ const skillPath = join(skillsDir, entry.category, entry.name, "SKILL.md");
64
+ const content = readFileSync(skillPath, "utf-8");
65
+ return content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "").trim();
66
+ }
67
+ function die(message) {
68
+ console.error(message);
69
+ process.exit(1);
70
+ }
71
+ function isAgentClient(value) {
72
+ return AGENT_CLIENTS.includes(value);
73
+ }
74
+ function isSkillScope(value) {
75
+ return SKILL_SCOPES.includes(value);
76
+ }
77
+ function parseAgent(value) {
78
+ if (isAgentClient(value))
79
+ return value;
80
+ die(`Unknown agent: ${value}. Use one of: ${AGENT_CLIENTS.join(", ")}.`);
81
+ }
82
+ function parseScope(value) {
83
+ if (isSkillScope(value))
84
+ return value;
85
+ die(`Unknown scope: ${value}. Use user or project.`);
86
+ }
87
+ function resolveAgent(opts) {
88
+ if (opts.asClaudeSkill && opts.asCursorSkill) {
89
+ die("Use only one legacy alias: --as-claude-skill or --as-cursor-skill.");
90
+ }
91
+ const legacyAgent = opts.asClaudeSkill
92
+ ? "claude-code"
93
+ : opts.asCursorSkill
94
+ ? "cursor"
95
+ : undefined;
96
+ if (opts.agent && legacyAgent && opts.agent !== legacyAgent) {
97
+ die(`Conflicting agent options: --agent ${opts.agent} with legacy alias for ${legacyAgent}.`);
98
+ }
99
+ return parseAgent(opts.agent ?? legacyAgent ?? "summer");
100
+ }
101
+ function resolveScope(agent, opts) {
102
+ if (opts.scope)
103
+ return parseScope(opts.scope);
104
+ if (agent === "cursor" || agent === "windsurf")
105
+ return "project";
106
+ return "user";
107
+ }
108
+ function agentLabel(agent) {
109
+ switch (agent) {
110
+ case "claude-code":
111
+ return "Claude Code";
112
+ case "codex":
113
+ return "Codex";
114
+ case "cursor":
115
+ return "Cursor";
116
+ case "windsurf":
117
+ return "Windsurf";
118
+ case "summer":
119
+ return "Summer";
120
+ }
121
+ }
122
+ function resolveInstallLocation(agent, scope) {
123
+ const overrideDir = process.env.SUMMER_SKILLS_DIR;
124
+ if (overrideDir) {
125
+ if (agent === "cursor")
126
+ return { kind: "cursor-rule-dir", path: overrideDir };
127
+ if (agent === "windsurf") {
128
+ return { kind: "windsurf-rule-file", path: join(overrideDir, ".windsurfrules") };
129
+ }
130
+ return { kind: "skill-dir", path: overrideDir };
131
+ }
132
+ const root = scope === "user" ? homedir() : process.cwd();
133
+ switch (agent) {
134
+ case "codex":
135
+ return { kind: "skill-dir", path: join(root, ".agents", "skills") };
136
+ case "claude-code":
137
+ return { kind: "skill-dir", path: join(root, ".claude", "skills") };
138
+ case "cursor":
139
+ return { kind: "cursor-rule-dir", path: join(root, ".cursor", "rules") };
140
+ case "windsurf":
141
+ return { kind: "windsurf-rule-file", path: join(root, ".windsurfrules") };
142
+ case "summer":
143
+ return { kind: "skill-dir", path: join(root, ".summer", "skills") };
144
+ }
145
+ }
146
+ function selectSkills(name, opts) {
147
+ if (opts.all && opts.recommended) {
148
+ die("Use only one bulk option: --all or --recommended.");
149
+ }
150
+ if (name && (opts.all || opts.recommended)) {
151
+ die("Specify either a skill name or a bulk option, not both.");
152
+ }
153
+ const skills = getBuiltinSkills();
154
+ if (opts.all)
155
+ return skills;
156
+ if (opts.recommended)
157
+ return skills.filter((skill) => skill.recommended);
158
+ if (!name) {
159
+ console.error("Specify a skill name, --recommended, or --all.");
160
+ printAvailableSkillNames();
161
+ process.exit(1);
162
+ }
163
+ const skill = skills.find((candidate) => candidate.name === name);
164
+ if (!skill) {
165
+ console.error(`Unknown skill: ${name}`);
166
+ printAvailableSkillNames();
167
+ process.exit(1);
168
+ }
169
+ return [skill];
170
+ }
171
+ function printAvailableSkillNames() {
172
+ const skills = getBuiltinSkills();
173
+ if (skills.length === 0)
174
+ return;
175
+ console.log("\nAvailable skills:");
176
+ for (const skill of skills) {
177
+ console.log(` ${skill.name}`);
178
+ }
179
+ }
180
+ function installSkill(skill, agent, location) {
181
+ if (!skill.clients.includes(agent)) {
182
+ die(`${skill.name} does not support ${agentLabel(agent)}.`);
183
+ }
184
+ switch (location.kind) {
185
+ case "skill-dir":
186
+ return copySkillDirectory(skill, location.path);
187
+ case "cursor-rule-dir":
188
+ return writeCursorRule(skill, location.path);
189
+ case "windsurf-rule-file":
190
+ return upsertWindsurfRule(skill, location.path);
191
+ }
192
+ }
193
+ function copySkillDirectory(skill, targetDir) {
194
+ const src = getSkillPath(skill.name);
195
+ if (!src)
196
+ die(`Skill files missing: ${skill.name}`);
197
+ mkdirSync(targetDir, { recursive: true });
198
+ const dest = join(targetDir, skill.name);
199
+ cpSync(src, dest, { recursive: true });
200
+ return { action: "Installed", path: dest };
201
+ }
202
+ function writeCursorRule(skill, rulesDir) {
203
+ mkdirSync(rulesDir, { recursive: true });
204
+ const rulePath = join(rulesDir, `summer-${skill.name}.mdc`);
205
+ writeFileSync(rulePath, renderCursorRule(skill), "utf-8");
206
+ return { action: "Generated", path: rulePath };
207
+ }
208
+ function upsertWindsurfRule(skill, rulePath) {
209
+ mkdirSync(dirname(rulePath), { recursive: true });
210
+ const start = `<!-- summer-skill:start:${skill.name} -->`;
211
+ const end = `<!-- summer-skill:end:${skill.name} -->`;
212
+ const block = `${start}\n${renderWindsurfRule(skill)}\n${end}`;
213
+ const existing = existsSync(rulePath) ? readFileSync(rulePath, "utf-8") : "";
214
+ const pattern = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`);
215
+ const next = pattern.test(existing)
216
+ ? existing.replace(pattern, block)
217
+ : `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${block}\n`;
218
+ writeFileSync(rulePath, next, "utf-8");
219
+ return { action: "Generated", path: rulePath };
220
+ }
221
+ function renderCursorRule(skill) {
222
+ const description = `Summer skill ${skill.name}: ${skill.description}`;
223
+ return `---\ndescription: ${JSON.stringify(description)}\nglobs: []\nalwaysApply: false\n---\n\n${renderRuleBody(skill)}\n`;
224
+ }
225
+ function renderWindsurfRule(skill) {
226
+ return renderRuleBody(skill);
227
+ }
228
+ function renderRuleBody(skill) {
229
+ return `# Summer skill: ${skill.name}
230
+
231
+ ${skill.description}
232
+
233
+ Use Summer MCP tools for scene, editor, asset, play, and diagnostics operations. Use the host agent's normal file editing tools for scripts and other text files.
234
+
235
+ ## Skill
236
+
237
+ ${getSkillBody(skill.name)}
238
+ `;
239
+ }
240
+ function escapeRegExp(value) {
241
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
242
+ }
243
+ function printInstallSummary(count, agent, scope, location) {
244
+ const label = agentLabel(agent);
245
+ console.log(`\n${count} skill${count === 1 ? "" : "s"} ready for ${label} (${scope} scope).`);
246
+ if (location.kind === "skill-dir") {
247
+ console.log(`${label} can read skills from ${location.path}/<skill>/SKILL.md`);
248
+ }
249
+ else if (location.kind === "cursor-rule-dir") {
250
+ console.log(`Cursor rules are in ${location.path}/summer-<skill>.mdc`);
251
+ }
252
+ else {
253
+ console.log(`Windsurf rules are in ${location.path}`);
254
+ }
255
+ }
49
256
  export const skillsCommand = new Command("skills")
50
257
  .description("Install and manage best-practice guides for AI agents building games")
51
258
  .action(() => {
@@ -60,124 +267,62 @@ skillsCommand
60
267
  console.log("No skills found.");
61
268
  return;
62
269
  }
63
- console.log("Available skills:\n");
270
+ console.log("Available public skills:\n");
64
271
  for (const s of skills) {
65
- console.log(` ${s.name.padEnd(20)} ${s.description}`);
272
+ const badge = s.recommended ? "recommended" : "optional";
273
+ console.log(` ${s.name.padEnd(20)} ${s.category.padEnd(10)} ${badge.padEnd(11)} ${s.description}`);
66
274
  }
67
- console.log("\nInstall: summer skills install <name>");
275
+ console.log("\nInstall recommended: summer skills install --recommended");
276
+ console.log("Install one: summer skills install <name>");
277
+ console.log(`Agents: ${AGENT_CLIENTS.join(", ")}`);
68
278
  });
69
- function resolveInstallDir(target) {
70
- if (process.env.SUMMER_SKILLS_DIR) {
71
- return process.env.SUMMER_SKILLS_DIR;
72
- }
73
- if (target === "claude") {
74
- return join(homedir(), ".claude", "skills");
75
- }
76
- if (target === "cursor") {
77
- return join(homedir(), ".cursor", "skills");
78
- }
79
- return join(homedir(), ".summer", "skills");
80
- }
81
- function getTargetFromOpts(opts) {
82
- if (opts.asClaudeSkill)
83
- return "claude";
84
- if (opts.asCursorSkill)
85
- return "cursor";
86
- return "summer";
87
- }
88
279
  skillsCommand
89
280
  .command("install [name]")
90
- .description("Install a skill to ~/.summer/skills/ (or use --as-claude-skill / --as-cursor-skill)")
281
+ .description("Install Summer skills for a target agent")
91
282
  .option("--all", "Install all available skills")
92
- .option("--as-claude-skill", "Install to ~/.claude/skills/ for Claude Code auto-discovery")
93
- .option("--as-cursor-skill", "Install to ~/.cursor/skills/ for Cursor auto-discovery")
283
+ .option("--recommended", "Install recommended public skills (excludes make-game)")
284
+ .option("--agent <agent>", `Target agent: ${AGENT_CLIENTS.join("|")}`)
285
+ .option("--scope <scope>", "Install scope: user or project")
286
+ .option("--as-claude-skill", "Legacy alias for --agent claude-code")
287
+ .option("--as-cursor-skill", "Legacy alias for --agent cursor")
94
288
  .action((name, opts) => {
95
- const { all } = opts;
96
- const target = getTargetFromOpts(opts);
97
- const targetDir = resolveInstallDir(target);
98
- if (all) {
99
- const skills = getBuiltinSkills();
100
- if (skills.length === 0) {
101
- console.log("No skills found.");
102
- return;
103
- }
104
- if (!existsSync(targetDir)) {
105
- mkdirSync(targetDir, { recursive: true });
106
- }
107
- for (const s of skills) {
108
- const src = getSkillPath(s.name);
109
- if (src) {
110
- const dest = join(targetDir, s.name);
111
- cpSync(src, dest, { recursive: true });
112
- console.log(` Installed ${s.name}`);
113
- }
114
- }
115
- console.log(`\nInstalled ${skills.length} skills to ${targetDir}`);
116
- if (target === "claude") {
117
- console.log("Claude Code will auto-discover these skills.");
118
- }
119
- else if (target === "cursor") {
120
- console.log("Cursor will auto-discover these skills.");
121
- }
122
- else {
123
- console.log("AI agents can read: <dir>/<name>/SKILL.md");
124
- }
289
+ const agent = resolveAgent(opts);
290
+ const scope = resolveScope(agent, opts);
291
+ const location = resolveInstallLocation(agent, scope);
292
+ const skills = selectSkills(name, opts);
293
+ if (skills.length === 0) {
294
+ console.log("No skills found.");
125
295
  return;
126
296
  }
127
- if (!name) {
128
- console.error("Specify a skill name or use --all to install all skills.");
129
- console.log("\nAvailable skills:");
130
- for (const s of getBuiltinSkills()) {
131
- console.log(` ${s.name}`);
132
- }
133
- process.exit(1);
134
- }
135
- const src = getSkillPath(name);
136
- if (!src) {
137
- console.error(`Unknown skill: ${name}`);
138
- const skills = getBuiltinSkills();
139
- if (skills.length > 0) {
140
- console.log("\nAvailable skills:");
141
- for (const s of skills) {
142
- console.log(` ${s.name}`);
143
- }
144
- }
145
- process.exit(1);
146
- }
147
- if (!existsSync(targetDir)) {
148
- mkdirSync(targetDir, { recursive: true });
149
- }
150
- const dest = join(targetDir, name);
151
- cpSync(src, dest, { recursive: true });
152
- console.log(`Installed ${name} to ${dest}`);
153
- if (target === "claude") {
154
- console.log("Claude Code will auto-discover this skill.");
155
- }
156
- else if (target === "cursor") {
157
- console.log("Cursor will auto-discover this skill.");
158
- }
159
- else {
160
- console.log(`\nAI agents can read: ${dest}/SKILL.md`);
297
+ for (const skill of skills) {
298
+ const result = installSkill(skill, agent, location);
299
+ console.log(` ${result.action} ${skill.name} -> ${result.path}`);
161
300
  }
301
+ printInstallSummary(skills.length, agent, scope, location);
162
302
  });
163
303
  skillsCommand
164
304
  .command("info <name>")
165
305
  .description("Show skill description and preview")
166
306
  .action((name) => {
167
- const src = getSkillPath(name);
168
- if (!src) {
307
+ const meta = getSkillMeta(name);
308
+ if (!meta) {
169
309
  console.error(`Unknown skill: ${name}`);
170
310
  process.exit(1);
171
311
  }
312
+ const src = getSkillPath(name);
313
+ if (!src)
314
+ die(`Skill files missing: ${name}`);
172
315
  const skillPath = join(src, "SKILL.md");
173
316
  const content = readFileSync(skillPath, "utf-8");
174
- const meta = parseSkillFrontmatter(skillPath);
175
- if (meta) {
176
- console.log(`\n${meta.name}`);
177
- console.log("─".repeat(40));
178
- console.log(meta.description);
179
- console.log("\n" + "─".repeat(40));
180
- }
317
+ console.log(`\n${meta.name}`);
318
+ console.log("-".repeat(40));
319
+ console.log(meta.description);
320
+ console.log(`Category: ${meta.category}`);
321
+ console.log(`Recommended: ${meta.recommended ? "yes" : "no"}`);
322
+ console.log(`Agents: ${meta.clients.join(", ")}`);
323
+ console.log(`MCP tools: ${meta.requiresMcpTools.length > 0 ? meta.requiresMcpTools.join(", ") : "none"}`);
324
+ console.log(`Test scenario: ${meta.testScenario}`);
325
+ console.log("\n" + "-".repeat(40));
181
326
  const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
182
327
  const preview = body.split("\n").slice(0, 30).join("\n");
183
328
  console.log(preview);
@@ -0,0 +1,39 @@
1
+ export declare const SUMMER_MCP_SERVER_NAME = "summer-engine";
2
+ export declare const supportedAgents: readonly ["codex", "claude-code", "cursor", "windsurf"];
3
+ export type SupportedAgent = (typeof supportedAgents)[number];
4
+ export type ConfigScope = "user" | "project";
5
+ export interface StdioMcpServerConfig {
6
+ command: string;
7
+ args: string[];
8
+ env?: Record<string, string>;
9
+ }
10
+ export interface AgentConfigOptions {
11
+ agent: SupportedAgent;
12
+ scope: ConfigScope;
13
+ dryRun?: boolean;
14
+ print?: boolean;
15
+ localDev?: boolean;
16
+ cwd?: string;
17
+ env?: NodeJS.ProcessEnv;
18
+ }
19
+ export interface AgentConfigResult {
20
+ agent: SupportedAgent;
21
+ scope: ConfigScope;
22
+ path: string;
23
+ serverName: string;
24
+ server: StdioMcpServerConfig;
25
+ format: "json" | "toml";
26
+ snippet: string;
27
+ changed: boolean;
28
+ wrote: boolean;
29
+ dryRun: boolean;
30
+ print: boolean;
31
+ localDev: boolean;
32
+ warnings: string[];
33
+ nextSteps: string[];
34
+ }
35
+ export declare function parseAgent(value: string | undefined): SupportedAgent | null;
36
+ export declare function parseScope(value: string | undefined): ConfigScope | null;
37
+ export declare function configureAgentMcp(options: AgentConfigOptions): Promise<AgentConfigResult>;
38
+ export declare function createSummerMcpServerConfig(localDev: boolean): StdioMcpServerConfig;
39
+ export declare function renderConfigSnippet(agent: SupportedAgent, server: StdioMcpServerConfig): string;