summer-engine 1.4.0 → 2.0.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 (63) hide show
  1. package/.claude-plugin/plugin.json +18 -10
  2. package/.codex-plugin/plugin.json +36 -8
  3. package/.cursor-plugin/plugin.json +19 -11
  4. package/.opencode/INSTALL.md +69 -0
  5. package/.opencode/plugins/summer.js +9 -4
  6. package/CHANGELOG.md +31 -1
  7. package/README.md +268 -86
  8. package/dist/bin/summer.js +0 -0
  9. package/dist/commands/assets.d.ts +2 -0
  10. package/dist/commands/assets.js +189 -0
  11. package/dist/commands/create.js +55 -17
  12. package/dist/commands/debug.d.ts +2 -0
  13. package/dist/commands/debug.js +127 -0
  14. package/dist/commands/list.js +24 -5
  15. package/dist/commands/project.d.ts +2 -0
  16. package/dist/commands/project.js +170 -0
  17. package/dist/commands/scene.d.ts +2 -0
  18. package/dist/commands/scene.js +290 -0
  19. package/dist/lib/banner.js +2 -2
  20. package/dist/lib/log-filters.d.ts +53 -0
  21. package/dist/lib/log-filters.js +130 -0
  22. package/dist/lib/plugin-manifests.test.d.ts +1 -0
  23. package/dist/lib/plugin-manifests.test.js +134 -0
  24. package/dist/lib/remote-templates.d.ts +25 -0
  25. package/dist/lib/remote-templates.js +86 -0
  26. package/dist/lib/skills-registry.d.ts +33 -1
  27. package/dist/lib/skills-registry.js +56 -1
  28. package/dist/lib/telemetry.d.ts +13 -0
  29. package/dist/lib/telemetry.js +63 -0
  30. package/dist/lib/with-engine.d.ts +14 -0
  31. package/dist/lib/with-engine.js +109 -0
  32. package/dist/mcp/tools/asset-tools.js +12 -13
  33. package/dist/mcp/tools/debug-tools.js +32 -9
  34. package/dist/mcp/tools/with-engine.js +4 -0
  35. package/gemini-extension.json +2 -2
  36. package/hooks/pre-commit-doctor.sh +0 -0
  37. package/hooks/run-hook.cmd +0 -0
  38. package/hooks/session-start.sh +0 -0
  39. package/marketplace.json +2 -2
  40. package/package.json +3 -3
  41. package/skills/README.md +1 -1
  42. package/skills/_meta/skill-create/SKILL.md +1 -1
  43. package/skills/_meta/skill-improve/SKILL.md +1 -1
  44. package/skills/_meta/skill-test/SKILL.md +1 -1
  45. package/skills/_meta/using-summer/SKILL.md +130 -0
  46. package/skills/_tests/specs/design-npc.md +43 -12
  47. package/skills/_tests/specs/fps-controller.md +33 -20
  48. package/skills/_tests/specs/host-authoritative-state.md +104 -0
  49. package/skills/_tests/specs/vfx.md +56 -38
  50. package/skills/ai-and-npcs/design-npc/SKILL.md +264 -122
  51. package/skills/asset-pipeline/asset-strategy/SKILL.md +1 -1
  52. package/skills/catalog.yaml +21 -4
  53. package/skills/character-controllers/fps-controller/SKILL.md +260 -79
  54. package/skills/debugging/debug/SKILL.md +102 -82
  55. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +332 -0
  56. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  57. package/skills/scene-and-project/browse-templates/SKILL.md +105 -0
  58. package/skills/scene-and-project/make-game/SKILL.md +159 -346
  59. package/skills/scene-and-project/new-project/SKILL.md +106 -0
  60. package/skills/scene-and-project/scene-composition/SKILL.md +1 -1
  61. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  62. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  63. package/skills/visual-effects/vfx/SKILL.md +233 -134
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Discover and clone open-source Summer Engine project templates hosted at
3
+ * github.com/SummerEngine/template-*.
4
+ *
5
+ * The GitHub API call is cached for one hour in memory to avoid rate-limit
6
+ * pain when an agent calls back-to-back.
7
+ */
8
+ import { execSync } from "node:child_process";
9
+ import { existsSync } from "node:fs";
10
+ const ORG = "SummerEngine";
11
+ const TEMPLATE_PREFIX = "template-";
12
+ const CACHE_TTL_MS = 60 * 60 * 1000;
13
+ let cache = null;
14
+ export async function fetchRemoteTemplates(opts = {}) {
15
+ if (!opts.force && cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
16
+ return cache.templates;
17
+ }
18
+ const url = `https://api.github.com/orgs/${ORG}/repos?per_page=100&type=public`;
19
+ const headers = {
20
+ Accept: "application/vnd.github+json",
21
+ "User-Agent": "summer-engine-cli",
22
+ };
23
+ if (process.env.GITHUB_TOKEN)
24
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
25
+ const res = await fetch(url, { headers });
26
+ if (!res.ok) {
27
+ throw new Error(`GitHub API ${res.status}: ${res.statusText}. ${res.status === 403 ? "Rate-limited; set GITHUB_TOKEN to raise the cap." : ""}`);
28
+ }
29
+ const repos = (await res.json());
30
+ const templates = repos
31
+ .filter((r) => r.name.startsWith(TEMPLATE_PREFIX) && !r.archived && !r.fork)
32
+ .map((r) => ({
33
+ slug: r.name.slice(TEMPLATE_PREFIX.length),
34
+ repo: r.name,
35
+ description: r.description ?? "",
36
+ branch: r.default_branch,
37
+ url: r.html_url,
38
+ cloneUrl: r.clone_url,
39
+ stars: r.stargazers_count,
40
+ updatedAt: r.updated_at,
41
+ }))
42
+ .sort((a, b) => b.stars - a.stars || a.slug.localeCompare(b.slug));
43
+ cache = { templates, fetchedAt: Date.now() };
44
+ return templates;
45
+ }
46
+ export function cloneTemplate(template, opts) {
47
+ if (existsSync(opts.targetDir)) {
48
+ throw new Error(`Directory already exists: ${opts.targetDir}`);
49
+ }
50
+ // Shallow clone for speed.
51
+ try {
52
+ execSync(`git clone --depth 1 --branch ${template.branch} ${template.cloneUrl} "${opts.targetDir}"`, {
53
+ stdio: ["ignore", "pipe", "pipe"],
54
+ });
55
+ }
56
+ catch (err) {
57
+ throw new Error(`git clone failed for ${template.repo}. Is git installed? ${err.message}`);
58
+ }
59
+ if (opts.detach !== false) {
60
+ // Default to detached: the user wants their own history.
61
+ const gitDir = `${opts.targetDir}/.git`;
62
+ if (existsSync(gitDir)) {
63
+ try {
64
+ execSync(process.platform === "win32" ? `rmdir /s /q "${gitDir}"` : `rm -rf "${gitDir}"`, {
65
+ stdio: "ignore",
66
+ });
67
+ }
68
+ catch {
69
+ // best effort — user can clean up manually if it fails
70
+ }
71
+ }
72
+ }
73
+ }
74
+ /** Quick match: exact slug, then prefix, then substring. Returns null if ambiguous or none. */
75
+ export function matchTemplate(query, templates) {
76
+ const exact = templates.find((t) => t.slug === query || t.repo === query);
77
+ if (exact)
78
+ return exact;
79
+ const prefix = templates.filter((t) => t.slug.startsWith(query));
80
+ if (prefix.length === 1)
81
+ return prefix[0];
82
+ const substring = templates.filter((t) => t.slug.includes(query));
83
+ if (substring.length === 1)
84
+ return substring[0];
85
+ return null;
86
+ }
@@ -85,7 +85,7 @@ export declare const SKILL_REGISTRY: readonly [{
85
85
  readonly category: "scene-and-project";
86
86
  readonly public: true;
87
87
  readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
88
- readonly recommended: false;
88
+ readonly recommended: true;
89
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"];
90
90
  readonly testScenario: "Build one narrow playable prototype from requirements through scene setup, host-edited scripts, and diagnostics.";
91
91
  }, {
@@ -96,6 +96,22 @@ export declare const SKILL_REGISTRY: readonly [{
96
96
  readonly recommended: true;
97
97
  readonly requiresMcpTools: readonly ["summer_get_project_context", "summer_get_scene_tree"];
98
98
  readonly testScenario: "Walk a fresh user from \"I want to make a game but I don't know what\" to a 1-page brief saved at .summer/GameSoul.md.";
99
+ }, {
100
+ readonly name: "new-project";
101
+ readonly category: "scene-and-project";
102
+ readonly public: true;
103
+ readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
104
+ readonly recommended: true;
105
+ readonly requiresMcpTools: readonly ["summer_get_project_context"];
106
+ readonly testScenario: "User says \"start a blank project\" — skill asks for the name, picks empty vs 3d-basic, runs summer create, opens the engine, and stops to ask what to build.";
107
+ }, {
108
+ readonly name: "browse-templates";
109
+ readonly category: "scene-and-project";
110
+ readonly public: true;
111
+ readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
112
+ readonly recommended: true;
113
+ readonly requiresMcpTools: readonly ["summer_get_project_context"];
114
+ readonly testScenario: "User says \"what templates exist?\" — skill runs summer list templates, presents 3-5 curated picks, asks the project name, and runs summer create <slug> <name>.";
99
115
  }, {
100
116
  readonly name: "design-mechanic";
101
117
  readonly category: "gameplay-mechanics";
@@ -160,6 +176,22 @@ export declare const SKILL_REGISTRY: readonly [{
160
176
  readonly recommended: true;
161
177
  readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_add_node", "summer_set_prop", "summer_connect_signal", "summer_save_scene", "summer_get_script_errors", "summer_project_setting", "summer_play", "summer_stop"];
162
178
  readonly testScenario: "Add co-op LAN multiplayer (2-4 players) using MultiplayerAPI + MultiplayerSpawner + MultiplayerSynchronizer + ENet; lock authority model up front.";
179
+ }, {
180
+ readonly name: "peer-to-peer-multiplayer";
181
+ readonly category: "multiplayer-and-networking";
182
+ readonly public: true;
183
+ readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
184
+ readonly recommended: true;
185
+ readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_create_scene", "summer_add_node", "summer_set_prop", "summer_connect_signal", "summer_save_scene", "summer_get_script_errors", "summer_project_setting"];
186
+ readonly testScenario: "Build the four-layer P2P architecture from the ground up (NetworkManager + GameState + RPC patterns + prediction/interpolation) for a 2-player co-op game.";
187
+ }, {
188
+ readonly name: "host-authoritative-state";
189
+ readonly category: "multiplayer-and-networking";
190
+ readonly public: true;
191
+ readonly clients: readonly ["summer", "codex", "claude-code", "cursor", "windsurf"];
192
+ readonly recommended: true;
193
+ readonly requiresMcpTools: readonly ["summer_get_scene_tree", "summer_inspect_node", "summer_save_scene", "summer_get_script_errors"];
194
+ readonly testScenario: "Walk a 17-field state ownership audit, design a HealthManager + ScoreManager with the canonical Manager pattern, and verify late-join state replay works.";
163
195
  }, {
164
196
  readonly name: "export-and-ship";
165
197
  readonly category: "deployment";
@@ -158,7 +158,7 @@ export const SKILL_REGISTRY = [
158
158
  category: "scene-and-project",
159
159
  public: true,
160
160
  clients: ALL_CLIENTS,
161
- recommended: false,
161
+ recommended: true,
162
162
  requiresMcpTools: [
163
163
  "summer_get_agent_playbook",
164
164
  "summer_get_project_context",
@@ -187,6 +187,28 @@ export const SKILL_REGISTRY = [
187
187
  ],
188
188
  testScenario: "Walk a fresh user from \"I want to make a game but I don't know what\" to a 1-page brief saved at .summer/GameSoul.md.",
189
189
  },
190
+ {
191
+ name: "new-project",
192
+ category: "scene-and-project",
193
+ public: true,
194
+ clients: ALL_CLIENTS,
195
+ recommended: true,
196
+ requiresMcpTools: [
197
+ "summer_get_project_context",
198
+ ],
199
+ testScenario: "User says \"start a blank project\" — skill asks for the name, picks empty vs 3d-basic, runs summer create, opens the engine, and stops to ask what to build.",
200
+ },
201
+ {
202
+ name: "browse-templates",
203
+ category: "scene-and-project",
204
+ public: true,
205
+ clients: ALL_CLIENTS,
206
+ recommended: true,
207
+ requiresMcpTools: [
208
+ "summer_get_project_context",
209
+ ],
210
+ testScenario: "User says \"what templates exist?\" — skill runs summer list templates, presents 3-5 curated picks, asks the project name, and runs summer create <slug> <name>.",
211
+ },
190
212
  {
191
213
  name: "design-mechanic",
192
214
  category: "gameplay-mechanics",
@@ -332,6 +354,39 @@ export const SKILL_REGISTRY = [
332
354
  ],
333
355
  testScenario: "Add co-op LAN multiplayer (2-4 players) using MultiplayerAPI + MultiplayerSpawner + MultiplayerSynchronizer + ENet; lock authority model up front.",
334
356
  },
357
+ {
358
+ name: "peer-to-peer-multiplayer",
359
+ category: "multiplayer-and-networking",
360
+ public: true,
361
+ clients: ALL_CLIENTS,
362
+ recommended: true,
363
+ requiresMcpTools: [
364
+ "summer_get_scene_tree",
365
+ "summer_inspect_node",
366
+ "summer_create_scene",
367
+ "summer_add_node",
368
+ "summer_set_prop",
369
+ "summer_connect_signal",
370
+ "summer_save_scene",
371
+ "summer_get_script_errors",
372
+ "summer_project_setting",
373
+ ],
374
+ testScenario: "Build the four-layer P2P architecture from the ground up (NetworkManager + GameState + RPC patterns + prediction/interpolation) for a 2-player co-op game.",
375
+ },
376
+ {
377
+ name: "host-authoritative-state",
378
+ category: "multiplayer-and-networking",
379
+ public: true,
380
+ clients: ALL_CLIENTS,
381
+ recommended: true,
382
+ requiresMcpTools: [
383
+ "summer_get_scene_tree",
384
+ "summer_inspect_node",
385
+ "summer_save_scene",
386
+ "summer_get_script_errors",
387
+ ],
388
+ testScenario: "Walk a 17-field state ownership audit, design a HealthManager + ScoreManager with the canonical Manager pattern, and verify late-join state replay works.",
389
+ },
335
390
  {
336
391
  name: "export-and-ship",
337
392
  category: "deployment",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Ping the telemetry endpoint once per MCP server lifetime.
3
+ *
4
+ * - No await: returns synchronously after kicking off the request.
5
+ * - No throw: every error is swallowed.
6
+ * - No retry: a single best-effort attempt.
7
+ * - Skips entirely if no auth token is on disk (anonymous users still work).
8
+ */
9
+ export declare function recordMcpSession(): void;
10
+ /**
11
+ * Reset the once-per-session guard. Test-only.
12
+ */
13
+ export declare function _resetTelemetryPingedForTests(): void;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Pure-telemetry beacon for MCP usage. Fire-and-forget, never blocks, never throws.
3
+ * One ping per MCP server lifetime — counts daily-active users, not per-tool calls.
4
+ *
5
+ * The endpoint /api/mcp/log-local-call always returns allowed:true and never gates.
6
+ * No quota. The user account is the only thing being attributed; the response is
7
+ * ignored entirely on the CLI side.
8
+ */
9
+ import { getAuthToken } from "./auth.js";
10
+ const TELEMETRY_URL = process.env.SUMMER_GATEWAY_URL?.replace(/\/+$/, "") ?? "https://www.summerengine.com";
11
+ const TELEMETRY_PATH = "/api/mcp/log-local-call";
12
+ const TIMEOUT_MS = 5000;
13
+ let pinged = false;
14
+ /**
15
+ * Ping the telemetry endpoint once per MCP server lifetime.
16
+ *
17
+ * - No await: returns synchronously after kicking off the request.
18
+ * - No throw: every error is swallowed.
19
+ * - No retry: a single best-effort attempt.
20
+ * - Skips entirely if no auth token is on disk (anonymous users still work).
21
+ */
22
+ export function recordMcpSession() {
23
+ if (pinged)
24
+ return;
25
+ pinged = true;
26
+ // Wrap the entire async pipeline in a void-discarded promise so the caller
27
+ // never accidentally awaits us.
28
+ void (async () => {
29
+ try {
30
+ const token = await getAuthToken();
31
+ if (!token)
32
+ return; // unauthenticated MCP usage is allowed; we just can't attribute it
33
+ const ctrl = new AbortController();
34
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
35
+ try {
36
+ await fetch(`${TELEMETRY_URL}${TELEMETRY_PATH}`, {
37
+ method: "POST",
38
+ signal: ctrl.signal,
39
+ headers: {
40
+ Authorization: `Bearer ${token}`,
41
+ "Content-Type": "application/json",
42
+ },
43
+ body: JSON.stringify({
44
+ client: "summer-cli",
45
+ event: "mcp_session_start",
46
+ }),
47
+ });
48
+ }
49
+ finally {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+ catch {
54
+ // never throws — telemetry is best-effort
55
+ }
56
+ })();
57
+ }
58
+ /**
59
+ * Reset the once-per-session guard. Test-only.
60
+ */
61
+ export function _resetTelemetryPingedForTests() {
62
+ pinged = false;
63
+ }
@@ -0,0 +1,14 @@
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>;
@@ -0,0 +1,109 @@
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
+ }
@@ -65,14 +65,6 @@ async function searchAssetsApi(params) {
65
65
  });
66
66
  const data = (await res.json());
67
67
  if (!res.ok) {
68
- if (res.status === 402) {
69
- return {
70
- error: "upgrade_required",
71
- message: (data.message || "Asset search requires a Pro subscription.") +
72
- "\nUpgrade at: https://www.summerengine.com/pricing\nFree tier includes all other MCP tools (scene editing, debugging, etc).",
73
- plan: data.plan,
74
- };
75
- }
76
68
  if (res.status === 401) {
77
69
  return {
78
70
  error: "unauthorized",
@@ -80,6 +72,13 @@ async function searchAssetsApi(params) {
80
72
  " The user needs to re-authenticate:\n npx summer-engine login --force",
81
73
  };
82
74
  }
75
+ if (res.status === 429) {
76
+ return {
77
+ error: "rate_limited",
78
+ message: (data.message || "Asset search rate limit hit. Wait a moment and try again.") +
79
+ " The public library is free, but rate-limited per user to prevent bulk scraping.",
80
+ };
81
+ }
83
82
  return {
84
83
  error: data.error || "Search failed",
85
84
  message: data.message || "Asset search failed",
@@ -88,17 +87,17 @@ async function searchAssetsApi(params) {
88
87
  return data;
89
88
  }
90
89
  export function registerAssetTools(server) {
91
- server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem.
90
+ server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem. **Free for all users** with rate limits.
92
91
 
93
92
  Sources:
94
- - "library" (default) — Public asset library (25k+ community assets). Requires Pro plan.
95
- - "my_assets" — Your own generated/uploaded assets. Free for all users. Query is optional.
93
+ - "library" (default) — Public asset library (25k+ community assets). Free.
94
+ - "my_assets" — Your own generated/uploaded assets. Free. Query is optional.
96
95
  - "all" — Search both library and your assets.
97
96
 
98
97
  Uses hybrid search: keywords + semantic similarity. Finds assets by name AND by meaning.
99
98
  Returns asset names, types, preview URLs, and import-ready file URLs.
100
99
 
101
- Requires authentication: run 'npx summer-engine login' first.`, {
100
+ Requires authentication (so we can attribute usage and apply per-user rate limits): run 'npx summer-engine login' first.`, {
102
101
  query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon'. For my_assets, can be empty to list recent."),
103
102
  assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("all").describe("Filter by asset type"),
104
103
  limit: z.number().default(10).describe("Max results (1-20)"),
@@ -110,7 +109,7 @@ Requires authentication: run 'npx summer-engine login' first.`, {
110
109
  content: [
111
110
  {
112
111
  type: "text",
113
- text: JSON.stringify({ error: result.error, message: result.message, plan: result.plan }, null, 2),
112
+ text: JSON.stringify({ error: result.error, message: result.message }, null, 2),
114
113
  },
115
114
  ],
116
115
  isError: true,
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { withEngine } from "./with-engine.js";
3
+ import { shapeEngineLogResponse } from "../../lib/log-filters.js";
3
4
  export function registerDebugTools(server) {
4
5
  server.tool("summer_get_diagnostics", `Quick overview of all errors and warnings from both the editor console and the runtime debugger. Returns error counts and a guidance message.
5
6
 
@@ -10,27 +11,49 @@ Typical workflow after making changes:
10
11
  2. If errors: summer_get_console or summer_get_debugger_errors for details
11
12
  3. Fix the issues
12
13
  4. summer_get_diagnostics again to verify`, {}, async () => withEngine(async (client) => client.getDiagnostics()));
13
- server.tool("summer_get_console", "Read recent messages from the editor's Output panel. Shows print statements, warnings, and errors from both the editor and scripts. Use after summer_get_diagnostics indicates console issues.", {
14
- max_lines: z.number().optional().default(100).describe("Max lines to return (default 100)"),
14
+ server.tool("summer_get_console", `Read recent messages from the editor's Output panel.
15
+
16
+ Output is post-processed for token economy: consecutive identical messages collapse into one entry with a "(×N)" count suffix, and the response carries a "_filter" summary so you can see what was hidden. Use errors_only=true (default) to drop info/std noise; use raw=true to bypass all shaping.
17
+
18
+ Use after summer_get_diagnostics indicates console issues.`, {
19
+ max_lines: z.number().optional().default(100).describe("Max lines to return after dedupe (default 100)"),
15
20
  filter: z.string().optional().describe("Only return lines containing this string"),
16
- type: z.enum(["error", "warning", "std", "editor"]).optional().describe("Filter by message type"),
17
- }, async ({ max_lines, filter, type }) => withEngine(async (client) => {
21
+ type: z.enum(["error", "warning", "std", "editor"]).optional().describe("Filter by message type at the engine level"),
22
+ errors_only: z.boolean().optional().default(true).describe("Drop info/std noise, keep errors and warnings (default true)"),
23
+ strict_errors: z.boolean().optional().default(false).describe("Drop warnings too — return errors only"),
24
+ raw: z.boolean().optional().default(false).describe("Bypass dedupe and level filtering — return engine output verbatim"),
25
+ }, async ({ max_lines, filter, type, errors_only, strict_errors, raw }) => withEngine(async (client) => {
18
26
  const op = { op: "GetConsoleOutput", max_lines };
19
27
  if (filter)
20
28
  op.filter = filter;
21
29
  if (type)
22
30
  op.type = type;
23
- return client.executeOps([op]);
31
+ const engineResult = await client.executeOps([op]);
32
+ if (raw)
33
+ return engineResult;
34
+ const { result } = shapeEngineLogResponse(engineResult, {
35
+ errorsOnly: errors_only,
36
+ errorsOnlyStrict: strict_errors,
37
+ maxEntries: max_lines,
38
+ });
39
+ return result;
24
40
  }));
25
41
  server.tool("summer_clear_console", "Clear the editor's Output panel. Useful before running the game to get a clean slate for error checking.", {}, async () => withEngine(async (client) => client.executeOps([{ op: "ClearConsoleOutput" }])));
26
- server.tool("summer_get_debugger_errors", "Read runtime errors and warnings from the debugger. These are errors that occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.", {
27
- max_errors: z.number().optional().default(50).describe("Max errors to return"),
42
+ server.tool("summer_get_debugger_errors", `Read runtime errors and warnings from the debugger. These are errors that occur while the game is running (null references, missing nodes, physics errors). Different from console output — these come from the debugger, not print statements.
43
+
44
+ Output is deduped: identical errors firing every frame collapse into one entry with a "(×N)" count suffix. A "_filter" summary tells you exactly what was collapsed or truncated. Use raw=true to bypass shaping when you really need every entry.`, {
45
+ max_errors: z.number().optional().default(50).describe("Max errors to return after dedupe"),
28
46
  include_stack: z.boolean().optional().describe("Include stack traces for each error"),
29
- }, async ({ max_errors, include_stack }) => withEngine(async (client) => {
47
+ raw: z.boolean().optional().default(false).describe("Bypass dedupe — return engine output verbatim"),
48
+ }, async ({ max_errors, include_stack, raw }) => withEngine(async (client) => {
30
49
  const op = { op: "GetDebuggerErrors", max_errors };
31
50
  if (include_stack !== undefined)
32
51
  op.include_stack = include_stack;
33
- return client.executeOps([op]);
52
+ const engineResult = await client.executeOps([op]);
53
+ if (raw)
54
+ return engineResult;
55
+ const { result } = shapeEngineLogResponse(engineResult, { maxEntries: max_errors });
56
+ return result;
34
57
  }));
35
58
  server.tool("summer_play", `Start running the game in the engine. The game runs inside Summer Engine's viewport.
36
59
 
@@ -1,4 +1,5 @@
1
1
  import { getClient, resetClient } from "../server.js";
2
+ import { recordMcpSession } from "../../lib/telemetry.js";
2
3
  function extractOpError(result) {
3
4
  if (!result || typeof result !== "object")
4
5
  return null;
@@ -24,6 +25,9 @@ function buildActionHint(message) {
24
25
  return null;
25
26
  }
26
27
  export async function withEngine(fn) {
28
+ // Best-effort, fire-and-forget: count this MCP session as DAU for attribution.
29
+ // No await, no throw, no quota gating.
30
+ recordMcpSession();
27
31
  try {
28
32
  const client = await getClient();
29
33
  const result = await fn(client);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "summer",
3
- "version": "1.4.0",
4
- "description": "AI game-dev studio for Godot: MCP bridge to the local Summer Engine plus a context primer that teaches Gemini the 19 Summer skill names. Use with the npm CLI for skill files on disk.",
3
+ "version": "2.0.0",
4
+ "description": "Superpowers for AI game dev. MCP bridge to the local Summer Engine plus a context primer that teaches Gemini the 22 Summer skills. Use with the npm CLI for skill files on disk.",
5
5
  "contextFileName": "GEMINI.md",
6
6
  "mcpServers": {
7
7
  "summer-engine": {
File without changes
File without changes
File without changes
package/marketplace.json CHANGED
@@ -7,8 +7,8 @@
7
7
  {
8
8
  "name": "summer",
9
9
  "source": "./",
10
- "version": "1.4.0",
11
- "description": "AI game-dev studio: 19 skills under summer: namespace, MCP bridge, session-start + pre-commit hooks.",
10
+ "version": "2.0.0",
11
+ "description": "Superpowers for AI game dev. 24 auto-trigger skills under summer: namespace, MCP bridge to the local Summer Engine, session-start + pre-commit hooks.",
12
12
  "author": { "name": "Summer Engine", "email": "founders@summerengine.com" },
13
13
  "category": "game-development",
14
14
  "tags": ["game-dev", "godot", "ai-game-engine"],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "1.4.0",
4
- "description": "CLI and MCP tools for Summer Engine — the AI-native game engine",
3
+ "version": "2.0.0",
4
+ "description": "Summer — superpowers for AI game dev. CLI, skills, and MCP tools for Summer Engine.",
5
5
  "keywords": [
6
6
  "game-engine",
7
7
  "godot",
@@ -14,7 +14,7 @@
14
14
  "homepage": "https://summerengine.com",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "https://github.com/SummerEngine/summer-engine-cli"
17
+ "url": "https://github.com/SummerEngine/summer"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "Summer Engine <founders@summerengine.com>",
package/skills/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The canonical agent-readable game-dev knowledge source for Claude Code, Cursor, Codex, and Windsurf.
4
4
 
5
- When an agent is asked to make a game in Godot, this is what it reads to learn how.
5
+ When an agent is asked to make a game in Summer Engine, this is what it reads to learn how.
6
6
 
7
7
  ## Layout
8
8
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: skill-create
3
- description: Bootstrap a new Summer skill with the canonical folder structure, frontmatter, and stub sections. Use when adding a new skill to the library. Trigger on "create skill", "add skill", "new skill".
3
+ description: Use when a contributor wants to add a new skill to the Summer library — bootstraps the canonical folder structure, frontmatter, and stub sections. Trigger on "create skill", "add skill", "new skill", "scaffold a skill".
4
4
  license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: _meta
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: skill-improve
3
- description: Iterate on an existing Summer skill using a parallel-eval harness — run the skill against a behavioral spec with and without proposed changes, compare outputs, ship the version that wins. Use to upgrade a skill that's underperforming. Trigger on "improve skill", "iterate on skill", "make this skill better".
3
+ description: Use when a contributor wants to upgrade a Summer skill that is underperforming or to fix a regression — runs the skill against a behavioral spec with and without proposed changes via parallel-eval harness and ships the version that wins. Trigger on "improve skill", "iterate on skill", "make this skill better".
4
4
  license: MIT
5
5
  compatibility: [Cursor, Claude Code, Windsurf, Codex]
6
6
  category: _meta