summer-engine 2.2.0 → 2.5.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 (176) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +20 -5
  3. package/.codex-plugin/plugin.json +15 -5
  4. package/.cursor-plugin/plugin.json +15 -5
  5. package/.factory-plugin/plugin.json +2 -2
  6. package/.opencode/INSTALL.md +1 -1
  7. package/CHANGELOG.md +27 -6
  8. package/CLAUDE.md +2 -2
  9. package/GEMINI.md +2 -3
  10. package/README.md +82 -58
  11. package/_persona/summer/SKILL.md +5 -6
  12. package/commands/gameskill.md +103 -0
  13. package/dist/bin/summer.js +12 -4
  14. package/dist/commands/create.js +2 -0
  15. package/dist/commands/doctor.js +71 -1
  16. package/dist/commands/install.d.ts +17 -0
  17. package/dist/commands/install.js +63 -4
  18. package/dist/commands/memory.d.ts +14 -0
  19. package/dist/commands/memory.js +196 -0
  20. package/dist/commands/setup.js +12 -2
  21. package/dist/commands/skills.js +205 -8
  22. package/dist/commands/status.js +3 -0
  23. package/dist/lib/agent-config.d.ts +2 -2
  24. package/dist/lib/agent-config.js +298 -5
  25. package/dist/lib/api-client.d.ts +13 -0
  26. package/dist/lib/api-client.js +63 -26
  27. package/dist/lib/async-op-lifecycle.d.ts +60 -0
  28. package/dist/lib/async-op-lifecycle.js +83 -0
  29. package/dist/lib/project-memory.d.ts +33 -0
  30. package/dist/lib/project-memory.js +220 -0
  31. package/dist/lib/skills-registry.d.ts +134 -61
  32. package/dist/lib/skills-registry.js +106 -4
  33. package/dist/lib/version-check.d.ts +106 -0
  34. package/dist/lib/version-check.js +338 -0
  35. package/dist/mcp/server.d.ts +2 -0
  36. package/dist/mcp/server.js +81 -0
  37. package/dist/mcp/tools/debug-tools.js +32 -2
  38. package/dist/mcp/tools/project-tools.js +18 -6
  39. package/dist/mcp/tools/with-engine.d.ts +14 -0
  40. package/dist/mcp/tools/with-engine.js +52 -7
  41. package/gemini-extension.json +2 -2
  42. package/hooks/pre-commit-doctor.sh +0 -0
  43. package/hooks/run-hook.cmd +0 -0
  44. package/hooks/session-start.sh +0 -0
  45. package/package.json +6 -4
  46. package/skills/2d-assets/character-portrait/SKILL.md +1 -1
  47. package/skills/2d-assets/concept-art/SKILL.md +1 -1
  48. package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
  49. package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
  50. package/skills/2d-assets/pixel-art/SKILL.md +1 -1
  51. package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
  52. package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
  53. package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
  54. package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
  55. package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
  56. package/skills/3d-assets/character-model/SKILL.md +1 -1
  57. package/skills/3d-assets/organic-model/SKILL.md +1 -1
  58. package/skills/3d-assets/prop-model/SKILL.md +1 -1
  59. package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
  60. package/skills/README.md +37 -30
  61. package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
  62. package/skills/animation/animation-tree/SKILL.md +2 -2
  63. package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
  64. package/skills/animation/generate-motion/SKILL.md +1 -1
  65. package/skills/animation/procedural-animation/SKILL.md +2 -2
  66. package/skills/audio/adaptive-music/SKILL.md +1 -1
  67. package/skills/audio/audio-direction/SKILL.md +5 -5
  68. package/skills/audio/sound-effect/SKILL.md +2 -2
  69. package/skills/audio/voice-line/SKILL.md +25 -3
  70. package/skills/character-controllers/fps-controller/SKILL.md +5 -5
  71. package/skills/debugging/debug/SKILL.md +39 -1
  72. package/skills/deployment/export-and-ship/SKILL.md +4 -4
  73. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
  74. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
  75. package/skills/level-design/design-level/SKILL.md +5 -5
  76. package/skills/level-design/scene-to-level/SKILL.md +125 -0
  77. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
  78. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
  79. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
  80. package/skills/performance/tune-performance/SKILL.md +5 -5
  81. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  82. package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
  83. package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
  84. package/skills/scene-and-project/play/SKILL.md +1 -1
  85. package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
  86. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
  87. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  88. package/skills/video/animated-loop/SKILL.md +1 -1
  89. package/skills/video/cinematic-cutscene/SKILL.md +1 -1
  90. package/skills/video/trailer-shot/SKILL.md +1 -1
  91. package/skills/visual-effects/game-feel/SKILL.md +6 -6
  92. package/skills/workflow/brainstorming/SKILL.md +164 -0
  93. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
  94. package/skills/workflow/brainstorming/scripts/helper.js +88 -0
  95. package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
  96. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
  97. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
  98. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
  99. package/skills/workflow/brainstorming/visual-companion.md +287 -0
  100. package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
  101. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
  102. package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
  103. package/skills/workflow/gameskill/SKILL.md +111 -0
  104. package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
  105. package/skills/workflow/investigating-bugs/SKILL.md +296 -0
  106. package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
  107. package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
  108. package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
  109. package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
  110. package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
  111. package/skills/workflow/investigating-bugs/test-academic.md +14 -0
  112. package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
  113. package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
  114. package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
  115. package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
  116. package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
  117. package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
  118. package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
  119. package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
  120. package/skills/workflow/verification-before-completion/SKILL.md +139 -0
  121. package/skills/workflow/writing-plans/SKILL.md +152 -0
  122. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
  123. package/skills/workflow/writing-skills/SKILL.md +655 -0
  124. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  125. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  126. package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  127. package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  128. package/skills/workflow/writing-skills/render-graphs.js +168 -0
  129. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  130. package/dist/commands/assets.d.ts +0 -2
  131. package/dist/commands/assets.js +0 -189
  132. package/dist/commands/debug.d.ts +0 -2
  133. package/dist/commands/debug.js +0 -127
  134. package/dist/commands/doctor.test.d.ts +0 -1
  135. package/dist/commands/doctor.test.js +0 -29
  136. package/dist/commands/project.d.ts +0 -2
  137. package/dist/commands/project.js +0 -170
  138. package/dist/commands/scene.d.ts +0 -2
  139. package/dist/commands/scene.js +0 -290
  140. package/dist/lib/agent-config.test.d.ts +0 -1
  141. package/dist/lib/agent-config.test.js +0 -80
  142. package/dist/lib/format.test.d.ts +0 -1
  143. package/dist/lib/format.test.js +0 -36
  144. package/dist/lib/plugin-manifests.test.d.ts +0 -1
  145. package/dist/lib/plugin-manifests.test.js +0 -134
  146. package/dist/lib/with-engine.d.ts +0 -14
  147. package/dist/lib/with-engine.js +0 -109
  148. package/skills/_shared/collaborative-protocol.md +0 -50
  149. package/skills/_shared/gd-style.md +0 -121
  150. package/skills/_shared/godot-version.md +0 -32
  151. package/skills/_shared/mcp-tools-reference.md +0 -133
  152. package/skills/_shared/summer-folder.md +0 -44
  153. package/skills/_shared/template-registry.md +0 -40
  154. package/skills/_tests/runner.md +0 -90
  155. package/skills/_tests/specs/3d-lighting.md +0 -19
  156. package/skills/_tests/specs/art-direction.md +0 -115
  157. package/skills/_tests/specs/asset-strategy.md +0 -19
  158. package/skills/_tests/specs/audio-direction.md +0 -123
  159. package/skills/_tests/specs/brainstorm-game.md +0 -99
  160. package/skills/_tests/specs/debug.md +0 -94
  161. package/skills/_tests/specs/design-level.md +0 -113
  162. package/skills/_tests/specs/design-mechanic.md +0 -112
  163. package/skills/_tests/specs/design-npc.md +0 -143
  164. package/skills/_tests/specs/export-and-ship.md +0 -114
  165. package/skills/_tests/specs/fps-controller.md +0 -113
  166. package/skills/_tests/specs/gdscript-patterns.md +0 -19
  167. package/skills/_tests/specs/host-authoritative-state.md +0 -104
  168. package/skills/_tests/specs/make-game.md +0 -19
  169. package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
  170. package/skills/_tests/specs/play.md +0 -88
  171. package/skills/_tests/specs/scene-composition.md +0 -19
  172. package/skills/_tests/specs/setup-multiplayer.md +0 -106
  173. package/skills/_tests/specs/tune-performance.md +0 -104
  174. package/skills/_tests/specs/ui-basics.md +0 -19
  175. package/skills/_tests/specs/vfx.md +0 -116
  176. package/skills/catalog.yaml +0 -991
@@ -1,290 +0,0 @@
1
- import { Command } from "commander";
2
- import { readFile } from "fs/promises";
3
- import { join } from "path";
4
- import { withEngineClient } from "../lib/with-engine.js";
5
- import { outputResult, outputError, parseValue, readJsonArg, isJsonMode, } from "../lib/format.js";
6
- async function readMainSceneFromProject(projectPath) {
7
- if (!projectPath)
8
- return null;
9
- try {
10
- const text = await readFile(join(projectPath, "project.godot"), "utf-8");
11
- const match = text.match(/run\/main_scene="([^"]+)"/);
12
- return match?.[1] ?? null;
13
- }
14
- catch {
15
- return null;
16
- }
17
- }
18
- export const sceneCommand = new Command("scene").description("Scene editing commands (requires running engine)");
19
- // --- Tier 1 ---
20
- sceneCommand
21
- .command("tree")
22
- .description("Get the full scene tree of the currently open scene")
23
- .action(async (_opts, cmd) => {
24
- const fmt = isJsonMode(cmd);
25
- try {
26
- const result = await withEngineClient((c) => c.getSceneState());
27
- outputResult(result, fmt);
28
- }
29
- catch (err) {
30
- outputError(err instanceof Error ? err.message : String(err), fmt);
31
- }
32
- });
33
- sceneCommand
34
- .command("open <path>")
35
- .description("Open a scene file in the editor (e.g. res://main.tscn)")
36
- .action(async (path, _opts, cmd) => {
37
- const fmt = isJsonMode(cmd);
38
- try {
39
- const result = await withEngineClient((c) => c.executeOps([{ op: "OpenScene", path }]));
40
- outputResult(result, fmt);
41
- }
42
- catch (err) {
43
- outputError(err instanceof Error ? err.message : String(err), fmt);
44
- }
45
- });
46
- sceneCommand
47
- .command("add <parent> <type> <name>")
48
- .description("Add a node to the scene tree (e.g. summer scene add ./World MeshInstance3D Floor)")
49
- .action(async (parent, type, name, _opts, cmd) => {
50
- const fmt = isJsonMode(cmd);
51
- try {
52
- const result = await withEngineClient((c) => c.executeOps([{ op: "AddNode", parent, type, name }]));
53
- outputResult(result, fmt);
54
- }
55
- catch (err) {
56
- outputError(err instanceof Error ? err.message : String(err), fmt);
57
- }
58
- });
59
- sceneCommand
60
- .command("prop <path> <key> <value>")
61
- .description('Set a property on a node (e.g. summer scene prop ./Player position "Vector3(0,5,0)")')
62
- .action(async (path, key, value, _opts, cmd) => {
63
- const fmt = isJsonMode(cmd);
64
- try {
65
- const result = await withEngineClient((c) => c.executeOps([{ op: "SetProp", path, key, value: parseValue(value) }]));
66
- outputResult(result, fmt);
67
- }
68
- catch (err) {
69
- outputError(err instanceof Error ? err.message : String(err), fmt);
70
- }
71
- });
72
- sceneCommand
73
- .command("save [path]")
74
- .description("Save the current scene (optionally save-as to a new path)")
75
- .action(async (path, _opts, cmd) => {
76
- const fmt = isJsonMode(cmd);
77
- try {
78
- const op = { op: "SaveScene" };
79
- if (path)
80
- op.path = path;
81
- const result = await withEngineClient((c) => c.executeOps([op]));
82
- outputResult(result, fmt);
83
- }
84
- catch (err) {
85
- outputError(err instanceof Error ? err.message : String(err), fmt);
86
- }
87
- });
88
- sceneCommand
89
- .command("inspect <path>")
90
- .description("Get all editable properties of a node (e.g. summer scene inspect Player)")
91
- .action(async (path, _opts, cmd) => {
92
- const fmt = isJsonMode(cmd);
93
- try {
94
- const result = await withEngineClient((c) => c.inspectNode(path));
95
- outputResult(result, fmt);
96
- }
97
- catch (err) {
98
- outputError(err instanceof Error ? err.message : String(err), fmt);
99
- }
100
- });
101
- sceneCommand
102
- .command("batch <json>")
103
- .description('Execute multiple ops in one undo step. Pass JSON array or "-" for stdin')
104
- .action(async (jsonArg, _opts, cmd) => {
105
- const fmt = isJsonMode(cmd);
106
- try {
107
- const ops = (await readJsonArg(jsonArg));
108
- const result = await withEngineClient((c) => c.executeOps(ops, { groupUndo: true }));
109
- outputResult(result, fmt);
110
- }
111
- catch (err) {
112
- outputError(err instanceof Error ? err.message : String(err), fmt);
113
- }
114
- });
115
- // --- Tier 2 ---
116
- sceneCommand
117
- .command("remove <path>")
118
- .description("Remove a node and all its children from the scene tree")
119
- .action(async (path, _opts, cmd) => {
120
- const fmt = isJsonMode(cmd);
121
- try {
122
- const result = await withEngineClient((c) => c.executeOps([{ op: "RemoveNode", path }]));
123
- outputResult(result, fmt);
124
- }
125
- catch (err) {
126
- outputError(err instanceof Error ? err.message : String(err), fmt);
127
- }
128
- });
129
- sceneCommand
130
- .command("rprop <nodePath> <resourceProperty> <subProperty> <value>")
131
- .description("Set a nested resource property (e.g. summer scene rprop ./Player/Col shape size \"Vector3(1,2,1)\")")
132
- .action(async (nodePath, resourceProperty, subProperty, value, _opts, cmd) => {
133
- const fmt = isJsonMode(cmd);
134
- try {
135
- const result = await withEngineClient((c) => c.executeOps([
136
- {
137
- op: "SetResourceProperty",
138
- nodePath,
139
- resourceProperty,
140
- subProperty,
141
- value: parseValue(value),
142
- },
143
- ]));
144
- outputResult(result, fmt);
145
- }
146
- catch (err) {
147
- outputError(err instanceof Error ? err.message : String(err), fmt);
148
- }
149
- });
150
- sceneCommand
151
- .command("instantiate <parent> <scene>")
152
- .description("Add an existing scene/model as a child (e.g. summer scene instantiate ./World res://player.tscn)")
153
- .option("--name <name>", "Override the instance name")
154
- .action(async (parent, scene, opts, cmd) => {
155
- const fmt = isJsonMode(cmd);
156
- try {
157
- const op = {
158
- op: "InstantiateScene",
159
- parent,
160
- scene,
161
- };
162
- if (opts.name)
163
- op.name = opts.name;
164
- const result = await withEngineClient((c) => c.executeOps([op]));
165
- outputResult(result, fmt);
166
- }
167
- catch (err) {
168
- outputError(err instanceof Error ? err.message : String(err), fmt);
169
- }
170
- });
171
- sceneCommand
172
- .command("create <path>")
173
- .description("Create a new empty scene file")
174
- .option("--root-name <name>", "Root node name", "Main")
175
- .option("--yes", "Skip safety confirmation")
176
- .action(async (path, opts, cmd) => {
177
- const fmt = isJsonMode(cmd);
178
- if (!opts.yes) {
179
- outputError("Scene creation uses a temporary mutation strategy. Pass --yes to confirm.", fmt);
180
- }
181
- try {
182
- const result = await withEngineClient(async (client) => {
183
- const health = (await client.health());
184
- const currentScene = typeof health.scene === "string" && health.scene.length > 0
185
- ? health.scene
186
- : null;
187
- const projectPath = typeof health.project_path === "string"
188
- ? health.project_path
189
- : undefined;
190
- const mainScene = await readMainSceneFromProject(projectPath);
191
- const templateScene = currentScene || mainScene;
192
- if (!templateScene) {
193
- throw new Error("No scene open and could not resolve main scene. Run `summer project context` first.");
194
- }
195
- await client.executeOps([{ op: "OpenScene", path: templateScene }]);
196
- const tree = (await client.getSceneState());
197
- const children = tree.data?.children ?? tree.children ?? [];
198
- const removeOps = children
199
- .map((c) => c.path)
200
- .filter((p) => typeof p === "string" && p.length > 0)
201
- .map((p) => ({ op: "RemoveNode", path: `./${p}` }));
202
- if (removeOps.length > 0) {
203
- await client.executeOps(removeOps, { groupUndo: true });
204
- }
205
- await client.executeOps([
206
- { op: "SetProp", path: ".", key: "name", value: opts.rootName },
207
- ]);
208
- await client.executeOps([{ op: "SaveScene", path }]);
209
- if (currentScene && currentScene !== path) {
210
- await client.executeOps([{ op: "OpenScene", path: currentScene }]);
211
- }
212
- return {
213
- ok: true,
214
- created: path,
215
- rootName: opts.rootName,
216
- templateScene,
217
- restoredScene: currentScene,
218
- };
219
- });
220
- outputResult(result, fmt);
221
- }
222
- catch (err) {
223
- outputError(err instanceof Error ? err.message : String(err), fmt);
224
- }
225
- });
226
- sceneCommand
227
- .command("replace <path>")
228
- .description("Replace a node with a different type or scene")
229
- .option("--type <type>", "New node type (e.g. RigidBody3D)")
230
- .option("--scene <scene>", "Scene to replace with (e.g. res://enemies/boss.tscn)")
231
- .action(async (path, opts, cmd) => {
232
- const fmt = isJsonMode(cmd);
233
- try {
234
- const op = { op: "ReplaceNode", path };
235
- if (opts.type)
236
- op.type = opts.type;
237
- if (opts.scene)
238
- op.scene = opts.scene;
239
- const result = await withEngineClient((c) => c.executeOps([op]));
240
- outputResult(result, fmt);
241
- }
242
- catch (err) {
243
- outputError(err instanceof Error ? err.message : String(err), fmt);
244
- }
245
- });
246
- sceneCommand
247
- .command("signal <emitter> <signal> <receiver> <method>")
248
- .description("Connect a signal between two nodes (e.g. summer scene signal ./HitArea body_entered ./Player _on_hit)")
249
- .action(async (emitter, signal, receiver, method, _opts, cmd) => {
250
- const fmt = isJsonMode(cmd);
251
- try {
252
- const result = await withEngineClient((c) => c.executeOps([
253
- { op: "ConnectSignal", emitter, signal, receiver, method },
254
- ]));
255
- outputResult(result, fmt);
256
- }
257
- catch (err) {
258
- outputError(err instanceof Error ? err.message : String(err), fmt);
259
- }
260
- });
261
- sceneCommand
262
- .command("select <nodePath>")
263
- .description("Select a node in the editor inspector")
264
- .option("--scene <scenePath>", "Open this scene first, then select")
265
- .action(async (nodePath, opts, cmd) => {
266
- const fmt = isJsonMode(cmd);
267
- try {
268
- const op = { op: "SelectNode", nodePath };
269
- if (opts.scene)
270
- op.scenePath = opts.scene;
271
- const result = await withEngineClient((c) => c.executeOps([op]));
272
- outputResult(result, fmt);
273
- }
274
- catch (err) {
275
- outputError(err instanceof Error ? err.message : String(err), fmt);
276
- }
277
- });
278
- sceneCommand
279
- .command("inspect-resource <path>")
280
- .description("Get all properties of a resource (material, mesh, shape, etc.)")
281
- .action(async (path, _opts, cmd) => {
282
- const fmt = isJsonMode(cmd);
283
- try {
284
- const result = await withEngineClient((c) => c.inspectResource(path));
285
- outputResult(result, fmt);
286
- }
287
- catch (err) {
288
- outputError(err instanceof Error ? err.message : String(err), fmt);
289
- }
290
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,80 +0,0 @@
1
- import { mkdtempSync, readFileSync, writeFileSync } from "fs";
2
- import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { describe, expect, it } from "vitest";
5
- import { configureAgentMcp } from "./agent-config.js";
6
- function tmp() {
7
- return mkdtempSync(join(tmpdir(), "summer-agent-config-"));
8
- }
9
- describe("configureAgentMcp", () => {
10
- it("writes a fresh claude-code config", async () => {
11
- const dir = tmp();
12
- const path = join(dir, ".claude.json");
13
- const result = await configureAgentMcp({
14
- agent: "claude-code",
15
- scope: "user",
16
- env: { SUMMER_CLAUDE_CONFIG_FILE: path },
17
- });
18
- expect(result.wrote).toBe(true);
19
- const written = JSON.parse(readFileSync(path, "utf-8"));
20
- expect(written.mcpServers["summer-engine"].command).toBe("npx");
21
- expect(written.mcpServers["summer-engine"].args).toEqual([
22
- "summer-engine",
23
- "mcp",
24
- ]);
25
- });
26
- it("preserves unrelated keys when merging claude-code config", async () => {
27
- const dir = tmp();
28
- const path = join(dir, ".claude.json");
29
- writeFileSync(path, JSON.stringify({
30
- theme: "dark",
31
- mcpServers: {
32
- other: { command: "node", args: ["other.js"] },
33
- },
34
- }, null, 2));
35
- await configureAgentMcp({
36
- agent: "claude-code",
37
- scope: "user",
38
- env: { SUMMER_CLAUDE_CONFIG_FILE: path },
39
- });
40
- const written = JSON.parse(readFileSync(path, "utf-8"));
41
- expect(written.theme).toBe("dark");
42
- expect(written.mcpServers.other.command).toBe("node");
43
- expect(written.mcpServers["summer-engine"].command).toBe("npx");
44
- });
45
- it("upserts a codex TOML server table", async () => {
46
- const dir = tmp();
47
- const path = join(dir, "config.toml");
48
- writeFileSync(path, ['[mcp_servers.other]', 'command = "node"', 'args = ["other.js"]', ''].join("\n"));
49
- const first = await configureAgentMcp({
50
- agent: "codex",
51
- scope: "user",
52
- env: { SUMMER_CODEX_CONFIG_FILE: path },
53
- });
54
- expect(first.wrote).toBe(true);
55
- let content = readFileSync(path, "utf-8");
56
- expect(content).toContain("[mcp_servers.other]");
57
- expect(content).toContain("[mcp_servers.summer-engine]");
58
- const second = await configureAgentMcp({
59
- agent: "codex",
60
- scope: "user",
61
- env: { SUMMER_CODEX_CONFIG_FILE: path },
62
- });
63
- expect(second.wrote).toBe(false);
64
- content = readFileSync(path, "utf-8");
65
- const occurrences = content.match(/\[mcp_servers\.summer-engine\]/g) ?? [];
66
- expect(occurrences.length).toBe(1);
67
- });
68
- it("dry-run does not write", async () => {
69
- const dir = tmp();
70
- const path = join(dir, ".claude.json");
71
- const result = await configureAgentMcp({
72
- agent: "claude-code",
73
- scope: "user",
74
- dryRun: true,
75
- env: { SUMMER_CLAUDE_CONFIG_FILE: path },
76
- });
77
- expect(result.wrote).toBe(false);
78
- expect(() => readFileSync(path, "utf-8")).toThrow();
79
- });
80
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,36 +0,0 @@
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 +0,0 @@
1
- export {};
@@ -1,134 +0,0 @@
1
- /**
2
- * Validates that every agent plugin manifest references real, on-disk skills,
3
- * and that every skill claimed HAVE in catalog.yaml actually exists.
4
- *
5
- * Catches broken-path bugs before they ship. Add a manifest, add a path here.
6
- */
7
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
- import { describe, expect, it } from "vitest";
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const repoRoot = join(__dirname, "..", "..");
13
- const MANIFESTS = [
14
- {
15
- name: ".claude-plugin/plugin.json",
16
- path: join(repoRoot, ".claude-plugin", "plugin.json"),
17
- skillsField: [],
18
- },
19
- {
20
- name: ".cursor-plugin/plugin.json",
21
- path: join(repoRoot, ".cursor-plugin", "plugin.json"),
22
- skillsField: [],
23
- },
24
- {
25
- name: ".codex-plugin/plugin.json",
26
- path: join(repoRoot, ".codex-plugin", "plugin.json"),
27
- skillsField: [],
28
- },
29
- ];
30
- function loadSkillsField(manifestPath) {
31
- const raw = readFileSync(manifestPath, "utf-8");
32
- const json = JSON.parse(raw);
33
- if (json.skills === undefined) {
34
- throw new Error(`${manifestPath}: missing "skills" field`);
35
- }
36
- return json.skills;
37
- }
38
- function listSkillDirs(skillsRoot) {
39
- const entries = readdirSync(skillsRoot);
40
- const out = [];
41
- for (const cat of entries) {
42
- const catPath = join(skillsRoot, cat);
43
- if (!statSync(catPath).isDirectory())
44
- continue;
45
- if (cat === "_shared" || cat === "_tests")
46
- continue;
47
- for (const skill of readdirSync(catPath)) {
48
- const skillPath = join(catPath, skill);
49
- if (!statSync(skillPath).isDirectory())
50
- continue;
51
- if (existsSync(join(skillPath, "SKILL.md"))) {
52
- out.push(`./skills/${cat}/${skill}/`);
53
- }
54
- }
55
- }
56
- return out;
57
- }
58
- describe("agent plugin manifests", () => {
59
- for (const m of MANIFESTS) {
60
- describe(m.name, () => {
61
- it("exists and parses as JSON", () => {
62
- expect(existsSync(m.path), `${m.name} not found`).toBe(true);
63
- m.skillsField = loadSkillsField(m.path);
64
- });
65
- it("every referenced skill path resolves to a SKILL.md on disk", () => {
66
- const skills = m.skillsField;
67
- if (typeof skills === "string") {
68
- // Directory pointer mode (e.g. "./skills/")
69
- const dir = join(repoRoot, skills);
70
- expect(existsSync(dir), `${m.name}: skills dir ${skills} missing`).toBe(true);
71
- return;
72
- }
73
- const broken = [];
74
- for (const rel of skills) {
75
- const skillMd = join(repoRoot, rel, "SKILL.md");
76
- if (!existsSync(skillMd))
77
- broken.push(rel);
78
- }
79
- expect(broken, `${m.name} references missing skills:\n${broken.join("\n")}`).toEqual([]);
80
- });
81
- it("does not duplicate skill paths", () => {
82
- const skills = m.skillsField;
83
- if (typeof skills === "string")
84
- return;
85
- const seen = new Set();
86
- const dupes = [];
87
- for (const rel of skills) {
88
- if (seen.has(rel))
89
- dupes.push(rel);
90
- seen.add(rel);
91
- }
92
- expect(dupes, `${m.name} duplicates: ${dupes.join(", ")}`).toEqual([]);
93
- });
94
- });
95
- }
96
- describe("skills directory <-> manifest coverage", () => {
97
- it("every on-disk skill is listed in .claude-plugin/plugin.json", () => {
98
- const onDisk = listSkillDirs(join(repoRoot, "skills"));
99
- const claudeManifest = JSON.parse(readFileSync(join(repoRoot, ".claude-plugin", "plugin.json"), "utf-8"));
100
- const listed = new Set(claudeManifest.skills);
101
- const orphans = onDisk.filter((p) => !listed.has(p));
102
- expect(orphans, `On-disk skills missing from .claude-plugin/plugin.json (add or move under skills/_shared|_tests):\n${orphans.join("\n")}`).toEqual([]);
103
- });
104
- });
105
- });
106
- describe("skill SKILL.md frontmatter", () => {
107
- it("every shipped SKILL.md has name and Use-when-style description", () => {
108
- const skillsRoot = join(repoRoot, "skills");
109
- const skillDirs = listSkillDirs(skillsRoot);
110
- const failures = [];
111
- for (const rel of skillDirs) {
112
- const file = join(repoRoot, rel, "SKILL.md");
113
- const text = readFileSync(file, "utf-8");
114
- const match = text.match(/^---\s*\n([\s\S]+?)\n---/);
115
- if (!match) {
116
- failures.push(`${rel}: no frontmatter`);
117
- continue;
118
- }
119
- const fm = match[1];
120
- const nameMatch = fm.match(/^name:\s*(.+)$/m);
121
- const descMatch = fm.match(/^description:\s*(.+)$/m);
122
- if (!nameMatch || !nameMatch[1].trim())
123
- failures.push(`${rel}: missing name`);
124
- if (!descMatch || !descMatch[1].trim())
125
- failures.push(`${rel}: missing description`);
126
- const desc = descMatch?.[1] ?? "";
127
- // Auto-trigger discipline: description must contain "Use when"
128
- if (desc && !/\bUse when\b/i.test(desc)) {
129
- failures.push(`${rel}: description must contain "Use when ..." (got: "${desc.slice(0, 80)}...")`);
130
- }
131
- }
132
- expect(failures, `Skill frontmatter problems:\n${failures.join("\n")}`).toEqual([]);
133
- });
134
- });
@@ -1,14 +0,0 @@
1
- import { EngineApiClient } from "./api-client.js";
2
- export declare function extractOpError(result: unknown): string | null;
3
- export declare function buildActionHint(message: string): string | null;
4
- export declare function checkQuota(): Promise<{
5
- allowed: boolean;
6
- message?: string;
7
- }>;
8
- export declare function getClient(): Promise<EngineApiClient>;
9
- export declare function resetClient(): void;
10
- /**
11
- * Connect to the engine, check quota, and run the given function.
12
- * Throws on errors (quota, connection, op errors).
13
- */
14
- export declare function withEngineClient<T>(fn: (client: EngineApiClient) => Promise<T>): Promise<T>;
@@ -1,109 +0,0 @@
1
- import { EngineApiClient } from "./api-client.js";
2
- import { getAuthToken } from "./auth.js";
3
- const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
4
- export function extractOpError(result) {
5
- if (!result || typeof result !== "object")
6
- return null;
7
- const op = result;
8
- if (op.status === "error" && op.error)
9
- return op.error;
10
- const firstFailed = op.results?.find((r) => r.ok === false && r.error);
11
- return firstFailed?.error ?? null;
12
- }
13
- export function buildActionHint(message) {
14
- const normalized = message.toLowerCase();
15
- if (normalized.includes("no scene open") || normalized.includes("no edited scene")) {
16
- return "No scene is currently open. Run `summer project context` first, then `summer project open-main` (or `summer scene open` with a known .tscn path).";
17
- }
18
- if (normalized.includes("failed to open scene")) {
19
- return "Scene path could not be opened. Run `summer project context` to get mainScene, then open that exact path. Avoid guessing scene filenames.";
20
- }
21
- if (normalized.includes("writefile cannot edit .tscn/.scn")) {
22
- return "Cannot write .tscn/.scn directly. Use summer scene commands for scene-tree operations, and file-edit tools for .gd scripts.";
23
- }
24
- return null;
25
- }
26
- export async function checkQuota() {
27
- const token = await getAuthToken();
28
- if (!token) {
29
- return {
30
- allowed: false,
31
- message: "Not signed in. Run: summer login\nOr open: https://www.summerengine.com/login\nEngine tools require a Summer Engine account (free to create).",
32
- };
33
- }
34
- try {
35
- const res = await fetch(`${GATEWAY_URL}/api/mcp/log-local-call`, {
36
- method: "POST",
37
- headers: {
38
- Authorization: `Bearer ${token}`,
39
- "Content-Type": "application/json",
40
- },
41
- body: JSON.stringify({}),
42
- signal: AbortSignal.timeout(10000),
43
- });
44
- const data = (await res.json().catch(() => ({})));
45
- if ([401, 402, 403, 429].includes(res.status)) {
46
- return {
47
- allowed: false,
48
- message: data.message || "Quota check failed. Try again.",
49
- };
50
- }
51
- // Soft-fail on service/gateway issues so local tools keep working.
52
- if (!res.ok) {
53
- return { allowed: true };
54
- }
55
- if (data.allowed === false) {
56
- return {
57
- allowed: false,
58
- message: data.message || "Call limit reached. Upgrade at https://summerengine.com/pricing",
59
- };
60
- }
61
- return { allowed: true };
62
- }
63
- catch {
64
- // Network/transient errors should not brick local usage.
65
- return { allowed: true };
66
- }
67
- }
68
- let cachedClient = null;
69
- export function getClient() {
70
- if (cachedClient)
71
- return Promise.resolve(cachedClient);
72
- return EngineApiClient.connect().then((c) => {
73
- cachedClient = c;
74
- return c;
75
- });
76
- }
77
- export function resetClient() {
78
- cachedClient = null;
79
- }
80
- /**
81
- * Connect to the engine, check quota, and run the given function.
82
- * Throws on errors (quota, connection, op errors).
83
- */
84
- export async function withEngineClient(fn) {
85
- const quota = await checkQuota();
86
- if (!quota.allowed) {
87
- throw new Error(quota.message || "Quota exceeded");
88
- }
89
- try {
90
- const client = await getClient();
91
- const result = await fn(client);
92
- const opError = extractOpError(result);
93
- if (opError) {
94
- const hint = buildActionHint(opError);
95
- throw new Error(hint ? `${opError}\n\nHint: ${hint}` : opError);
96
- }
97
- return result;
98
- }
99
- catch (err) {
100
- // Reset client on connection errors so next call retries
101
- if (err instanceof Error &&
102
- (err.message.includes("not running") ||
103
- err.message.includes("not responding") ||
104
- err.message.includes("ECONNREFUSED"))) {
105
- resetClient();
106
- }
107
- throw err;
108
- }
109
- }