summer-engine 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
16
  const skillPath = join(skillsDir, 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,209 @@ 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
51
  const path = join(skillsDir, name);
45
52
  if (!existsSync(path) || !existsSync(join(path, "SKILL.md")))
46
53
  return null;
47
54
  return path;
48
55
  }
56
+ function getSkillBody(name) {
57
+ const skillPath = join(skillsDir, name, "SKILL.md");
58
+ const content = readFileSync(skillPath, "utf-8");
59
+ return content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "").trim();
60
+ }
61
+ function die(message) {
62
+ console.error(message);
63
+ process.exit(1);
64
+ }
65
+ function isAgentClient(value) {
66
+ return AGENT_CLIENTS.includes(value);
67
+ }
68
+ function isSkillScope(value) {
69
+ return SKILL_SCOPES.includes(value);
70
+ }
71
+ function parseAgent(value) {
72
+ if (isAgentClient(value))
73
+ return value;
74
+ die(`Unknown agent: ${value}. Use one of: ${AGENT_CLIENTS.join(", ")}.`);
75
+ }
76
+ function parseScope(value) {
77
+ if (isSkillScope(value))
78
+ return value;
79
+ die(`Unknown scope: ${value}. Use user or project.`);
80
+ }
81
+ function resolveAgent(opts) {
82
+ if (opts.asClaudeSkill && opts.asCursorSkill) {
83
+ die("Use only one legacy alias: --as-claude-skill or --as-cursor-skill.");
84
+ }
85
+ const legacyAgent = opts.asClaudeSkill
86
+ ? "claude-code"
87
+ : opts.asCursorSkill
88
+ ? "cursor"
89
+ : undefined;
90
+ if (opts.agent && legacyAgent && opts.agent !== legacyAgent) {
91
+ die(`Conflicting agent options: --agent ${opts.agent} with legacy alias for ${legacyAgent}.`);
92
+ }
93
+ return parseAgent(opts.agent ?? legacyAgent ?? "summer");
94
+ }
95
+ function resolveScope(agent, opts) {
96
+ if (opts.scope)
97
+ return parseScope(opts.scope);
98
+ if (agent === "cursor" || agent === "windsurf")
99
+ return "project";
100
+ return "user";
101
+ }
102
+ function agentLabel(agent) {
103
+ switch (agent) {
104
+ case "claude-code":
105
+ return "Claude Code";
106
+ case "codex":
107
+ return "Codex";
108
+ case "cursor":
109
+ return "Cursor";
110
+ case "windsurf":
111
+ return "Windsurf";
112
+ case "summer":
113
+ return "Summer";
114
+ }
115
+ }
116
+ function resolveInstallLocation(agent, scope) {
117
+ const overrideDir = process.env.SUMMER_SKILLS_DIR;
118
+ if (overrideDir) {
119
+ if (agent === "cursor")
120
+ return { kind: "cursor-rule-dir", path: overrideDir };
121
+ if (agent === "windsurf") {
122
+ return { kind: "windsurf-rule-file", path: join(overrideDir, ".windsurfrules") };
123
+ }
124
+ return { kind: "skill-dir", path: overrideDir };
125
+ }
126
+ const root = scope === "user" ? homedir() : process.cwd();
127
+ switch (agent) {
128
+ case "codex":
129
+ return { kind: "skill-dir", path: join(root, ".agents", "skills") };
130
+ case "claude-code":
131
+ return { kind: "skill-dir", path: join(root, ".claude", "skills") };
132
+ case "cursor":
133
+ return { kind: "cursor-rule-dir", path: join(root, ".cursor", "rules") };
134
+ case "windsurf":
135
+ return { kind: "windsurf-rule-file", path: join(root, ".windsurfrules") };
136
+ case "summer":
137
+ return { kind: "skill-dir", path: join(root, ".summer", "skills") };
138
+ }
139
+ }
140
+ function selectSkills(name, opts) {
141
+ if (opts.all && opts.recommended) {
142
+ die("Use only one bulk option: --all or --recommended.");
143
+ }
144
+ if (name && (opts.all || opts.recommended)) {
145
+ die("Specify either a skill name or a bulk option, not both.");
146
+ }
147
+ const skills = getBuiltinSkills();
148
+ if (opts.all)
149
+ return skills;
150
+ if (opts.recommended)
151
+ return skills.filter((skill) => skill.recommended);
152
+ if (!name) {
153
+ console.error("Specify a skill name, --recommended, or --all.");
154
+ printAvailableSkillNames();
155
+ process.exit(1);
156
+ }
157
+ const skill = skills.find((candidate) => candidate.name === name);
158
+ if (!skill) {
159
+ console.error(`Unknown skill: ${name}`);
160
+ printAvailableSkillNames();
161
+ process.exit(1);
162
+ }
163
+ return [skill];
164
+ }
165
+ function printAvailableSkillNames() {
166
+ const skills = getBuiltinSkills();
167
+ if (skills.length === 0)
168
+ return;
169
+ console.log("\nAvailable skills:");
170
+ for (const skill of skills) {
171
+ console.log(` ${skill.name}`);
172
+ }
173
+ }
174
+ function installSkill(skill, agent, location) {
175
+ if (!skill.clients.includes(agent)) {
176
+ die(`${skill.name} does not support ${agentLabel(agent)}.`);
177
+ }
178
+ switch (location.kind) {
179
+ case "skill-dir":
180
+ return copySkillDirectory(skill, location.path);
181
+ case "cursor-rule-dir":
182
+ return writeCursorRule(skill, location.path);
183
+ case "windsurf-rule-file":
184
+ return upsertWindsurfRule(skill, location.path);
185
+ }
186
+ }
187
+ function copySkillDirectory(skill, targetDir) {
188
+ const src = getSkillPath(skill.name);
189
+ if (!src)
190
+ die(`Skill files missing: ${skill.name}`);
191
+ mkdirSync(targetDir, { recursive: true });
192
+ const dest = join(targetDir, skill.name);
193
+ cpSync(src, dest, { recursive: true });
194
+ return { action: "Installed", path: dest };
195
+ }
196
+ function writeCursorRule(skill, rulesDir) {
197
+ mkdirSync(rulesDir, { recursive: true });
198
+ const rulePath = join(rulesDir, `summer-${skill.name}.mdc`);
199
+ writeFileSync(rulePath, renderCursorRule(skill), "utf-8");
200
+ return { action: "Generated", path: rulePath };
201
+ }
202
+ function upsertWindsurfRule(skill, rulePath) {
203
+ mkdirSync(dirname(rulePath), { recursive: true });
204
+ const start = `<!-- summer-skill:start:${skill.name} -->`;
205
+ const end = `<!-- summer-skill:end:${skill.name} -->`;
206
+ const block = `${start}\n${renderWindsurfRule(skill)}\n${end}`;
207
+ const existing = existsSync(rulePath) ? readFileSync(rulePath, "utf-8") : "";
208
+ const pattern = new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`);
209
+ const next = pattern.test(existing)
210
+ ? existing.replace(pattern, block)
211
+ : `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${block}\n`;
212
+ writeFileSync(rulePath, next, "utf-8");
213
+ return { action: "Generated", path: rulePath };
214
+ }
215
+ function renderCursorRule(skill) {
216
+ const description = `Summer skill ${skill.name}: ${skill.description}`;
217
+ return `---\ndescription: ${JSON.stringify(description)}\nglobs: []\nalwaysApply: false\n---\n\n${renderRuleBody(skill)}\n`;
218
+ }
219
+ function renderWindsurfRule(skill) {
220
+ return renderRuleBody(skill);
221
+ }
222
+ function renderRuleBody(skill) {
223
+ return `# Summer skill: ${skill.name}
224
+
225
+ ${skill.description}
226
+
227
+ 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.
228
+
229
+ ## Skill
230
+
231
+ ${getSkillBody(skill.name)}
232
+ `;
233
+ }
234
+ function escapeRegExp(value) {
235
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
236
+ }
237
+ function printInstallSummary(count, agent, scope, location) {
238
+ const label = agentLabel(agent);
239
+ console.log(`\n${count} skill${count === 1 ? "" : "s"} ready for ${label} (${scope} scope).`);
240
+ if (location.kind === "skill-dir") {
241
+ console.log(`${label} can read skills from ${location.path}/<skill>/SKILL.md`);
242
+ }
243
+ else if (location.kind === "cursor-rule-dir") {
244
+ console.log(`Cursor rules are in ${location.path}/summer-<skill>.mdc`);
245
+ }
246
+ else {
247
+ console.log(`Windsurf rules are in ${location.path}`);
248
+ }
249
+ }
49
250
  export const skillsCommand = new Command("skills")
50
251
  .description("Install and manage best-practice guides for AI agents building games")
51
252
  .action(() => {
@@ -60,124 +261,62 @@ skillsCommand
60
261
  console.log("No skills found.");
61
262
  return;
62
263
  }
63
- console.log("Available skills:\n");
264
+ console.log("Available public skills:\n");
64
265
  for (const s of skills) {
65
- console.log(` ${s.name.padEnd(20)} ${s.description}`);
266
+ const badge = s.recommended ? "recommended" : "optional";
267
+ console.log(` ${s.name.padEnd(20)} ${s.category.padEnd(10)} ${badge.padEnd(11)} ${s.description}`);
66
268
  }
67
- console.log("\nInstall: summer skills install <name>");
269
+ console.log("\nInstall recommended: summer skills install --recommended");
270
+ console.log("Install one: summer skills install <name>");
271
+ console.log(`Agents: ${AGENT_CLIENTS.join(", ")}`);
68
272
  });
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
273
  skillsCommand
89
274
  .command("install [name]")
90
- .description("Install a skill to ~/.summer/skills/ (or use --as-claude-skill / --as-cursor-skill)")
275
+ .description("Install Summer skills for a target agent")
91
276
  .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")
277
+ .option("--recommended", "Install recommended public skills (excludes make-game)")
278
+ .option("--agent <agent>", `Target agent: ${AGENT_CLIENTS.join("|")}`)
279
+ .option("--scope <scope>", "Install scope: user or project")
280
+ .option("--as-claude-skill", "Legacy alias for --agent claude-code")
281
+ .option("--as-cursor-skill", "Legacy alias for --agent cursor")
94
282
  .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
- }
283
+ const agent = resolveAgent(opts);
284
+ const scope = resolveScope(agent, opts);
285
+ const location = resolveInstallLocation(agent, scope);
286
+ const skills = selectSkills(name, opts);
287
+ if (skills.length === 0) {
288
+ console.log("No skills found.");
125
289
  return;
126
290
  }
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`);
291
+ for (const skill of skills) {
292
+ const result = installSkill(skill, agent, location);
293
+ console.log(` ${result.action} ${skill.name} -> ${result.path}`);
161
294
  }
295
+ printInstallSummary(skills.length, agent, scope, location);
162
296
  });
163
297
  skillsCommand
164
298
  .command("info <name>")
165
299
  .description("Show skill description and preview")
166
300
  .action((name) => {
167
- const src = getSkillPath(name);
168
- if (!src) {
301
+ const meta = getSkillMeta(name);
302
+ if (!meta) {
169
303
  console.error(`Unknown skill: ${name}`);
170
304
  process.exit(1);
171
305
  }
306
+ const src = getSkillPath(name);
307
+ if (!src)
308
+ die(`Skill files missing: ${name}`);
172
309
  const skillPath = join(src, "SKILL.md");
173
310
  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
- }
311
+ console.log(`\n${meta.name}`);
312
+ console.log("-".repeat(40));
313
+ console.log(meta.description);
314
+ console.log(`Category: ${meta.category}`);
315
+ console.log(`Recommended: ${meta.recommended ? "yes" : "no"}`);
316
+ console.log(`Agents: ${meta.clients.join(", ")}`);
317
+ console.log(`MCP tools: ${meta.requiresMcpTools.length > 0 ? meta.requiresMcpTools.join(", ") : "none"}`);
318
+ console.log(`Test scenario: ${meta.testScenario}`);
319
+ console.log("\n" + "-".repeat(40));
181
320
  const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "");
182
321
  const preview = body.split("\n").slice(0, 30).join("\n");
183
322
  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;