summer-engine 4.0.0 → 4.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 (216) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +15 -11
  4. package/.cursor-plugin/plugin.json +5 -3
  5. package/.factory-plugin/plugin.json +3 -2
  6. package/AGENTS.md +2 -2
  7. package/CHANGELOG.md +48 -0
  8. package/README.md +21 -10
  9. package/dist/cli/command-usage.d.ts +8 -0
  10. package/dist/cli/command-usage.js +22 -0
  11. package/dist/cli/commands/config.js +3 -2
  12. package/dist/cli/commands/create.js +12 -3
  13. package/dist/cli/commands/list.d.ts +5 -0
  14. package/dist/cli/commands/list.js +30 -13
  15. package/dist/cli/commands/login.js +1 -1
  16. package/dist/cli/commands/memory.js +6 -2
  17. package/dist/cli/commands/ops.d.ts +20 -3
  18. package/dist/cli/commands/ops.js +60 -27
  19. package/dist/cli/commands/setup.d.ts +2 -0
  20. package/dist/cli/commands/setup.js +219 -16
  21. package/dist/cli/commands/skills.js +20 -7
  22. package/dist/cli/commands/status.d.ts +26 -0
  23. package/dist/cli/commands/status.js +36 -21
  24. package/dist/cli/commands/tool.js +4 -0
  25. package/dist/cli/index.js +2 -0
  26. package/dist/core/agent-host.d.ts +3 -0
  27. package/dist/core/agent-host.js +8 -2
  28. package/dist/core/capabilities/apps.d.ts +10 -10
  29. package/dist/core/capabilities/audit/args.d.ts +6 -6
  30. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  31. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  32. package/dist/core/capabilities/hdri-import.d.ts +2 -2
  33. package/dist/core/capabilities/motion-generation.d.ts +2 -2
  34. package/dist/core/capabilities/placement.d.ts +23 -23
  35. package/dist/core/capabilities/runtime-control.d.ts +38 -38
  36. package/dist/core/capabilities/seeing/args.d.ts +16 -16
  37. package/dist/core/capabilities/ui-control.d.ts +2 -2
  38. package/dist/core/capabilities/world-snapshot.d.ts +4 -4
  39. package/dist/core/cli-usage-beacon.d.ts +1 -0
  40. package/dist/core/cli-usage-beacon.js +18 -0
  41. package/dist/core/cli-usage.d.ts +49 -0
  42. package/dist/core/cli-usage.js +108 -0
  43. package/dist/core/feedback/client.d.ts +1 -1
  44. package/dist/core/feedback/client.js +3 -2
  45. package/dist/core/hosted-mcp.d.ts +7 -1
  46. package/dist/core/hosted-mcp.js +6 -4
  47. package/dist/core/library-search.d.ts +2 -2
  48. package/dist/core/ops/areas.d.ts +11 -0
  49. package/dist/core/ops/areas.js +24 -0
  50. package/dist/core/ops/catalog.d.ts +11 -0
  51. package/dist/core/ops/catalog.js +24 -1
  52. package/dist/core/ops/cloud.d.ts +10 -2
  53. package/dist/core/ops/cloud.js +51 -28
  54. package/dist/core/ops/contract.d.ts +2 -1
  55. package/dist/core/ops/contract.js +2 -10
  56. package/dist/core/ops/public-catalog.d.ts +48 -0
  57. package/dist/core/ops/public-catalog.js +56 -0
  58. package/dist/core/ops/search.d.ts +0 -1
  59. package/dist/core/ops/search.js +169 -37
  60. package/dist/core/telemetry.d.ts +10 -0
  61. package/dist/core/telemetry.js +58 -29
  62. package/dist/installer/agent-config.js +5 -0
  63. package/dist/installer/agent-table.d.ts +9 -0
  64. package/dist/installer/agent-table.js +23 -2
  65. package/dist/installer/cli-permissions.d.ts +65 -0
  66. package/dist/installer/cli-permissions.js +182 -0
  67. package/dist/installer/setup.d.ts +2 -0
  68. package/dist/installer/setup.js +3 -0
  69. package/dist/installer/skills-refresh.d.ts +29 -0
  70. package/dist/installer/skills-refresh.js +261 -0
  71. package/dist/installer/version-check.d.ts +22 -2
  72. package/dist/installer/version-check.js +66 -9
  73. package/dist/mcp/ops-surface.js +16 -6
  74. package/dist/mcp/server.d.ts +10 -0
  75. package/dist/mcp/server.js +38 -8
  76. package/dist/project-memory/agent-instructions.d.ts +47 -0
  77. package/dist/project-memory/agent-instructions.js +226 -0
  78. package/gemini-extension.json +3 -2
  79. package/integrations/README.md +1 -1
  80. package/integrations/claude/README.md +6 -0
  81. package/integrations/claude/manifest-target.json +4 -0
  82. package/integrations/gemini/README.md +11 -0
  83. package/integrations/windsurf/README.md +1 -1
  84. package/library/skills/3d-lighting/SKILL.md +16 -0
  85. package/library/skills/README.md +5 -4
  86. package/library/skills/adaptive-music/SKILL.md +21 -0
  87. package/library/skills/agent-playtesting/SKILL.md +22 -0
  88. package/library/skills/ai-characters-in-games/SKILL.md +15 -0
  89. package/library/skills/ambient-bed/SKILL.md +16 -0
  90. package/library/skills/animated-loop/SKILL.md +19 -0
  91. package/library/skills/animation-tree/SKILL.md +26 -0
  92. package/library/skills/art-direction/SKILL.md +14 -0
  93. package/library/skills/asset-strategy/SKILL.md +30 -0
  94. package/library/skills/audio-direction/SKILL.md +16 -0
  95. package/library/skills/auto-fire-targeting/SKILL.md +14 -0
  96. package/library/skills/brainstorm-game/SKILL.md +13 -0
  97. package/library/skills/browse-templates/SKILL.md +10 -0
  98. package/library/skills/camera-rigs/SKILL.md +20 -0
  99. package/library/skills/character-animation-wiring/SKILL.md +24 -0
  100. package/library/skills/character-model/SKILL.md +28 -0
  101. package/library/skills/character-movement/SKILL.md +24 -0
  102. package/library/skills/character-portrait/SKILL.md +15 -0
  103. package/library/skills/cinematic-cutscene/SKILL.md +21 -0
  104. package/library/skills/combat-basics/SKILL.md +19 -0
  105. package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
  106. package/library/skills/concept-art/SKILL.md +13 -0
  107. package/library/skills/concept-to-level/SKILL.md +20 -1
  108. package/library/skills/concept-to-level/resource.yaml +2 -2
  109. package/library/skills/create-asset-sheet/SKILL.md +18 -0
  110. package/library/skills/debug/SKILL.md +32 -0
  111. package/library/skills/debugging-game-feel/SKILL.md +12 -0
  112. package/library/skills/design-level/SKILL.md +17 -0
  113. package/library/skills/design-mechanic/SKILL.md +17 -0
  114. package/library/skills/design-npc/SKILL.md +21 -0
  115. package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
  116. package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
  117. package/library/skills/environment-kit/SKILL.md +23 -0
  118. package/library/skills/export-and-ship/SKILL.md +23 -0
  119. package/library/skills/fabricating-assets/SKILL.md +23 -0
  120. package/library/skills/facial-and-lipsync/SKILL.md +22 -0
  121. package/library/skills/fps-controller/SKILL.md +17 -0
  122. package/library/skills/game-feel/SKILL.md +26 -0
  123. package/library/skills/gameskill/SKILL.md +11 -0
  124. package/library/skills/gdscript-patterns/SKILL.md +15 -0
  125. package/library/skills/generate-motion/SKILL.md +21 -0
  126. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
  127. package/library/skills/grow-analytics/SKILL.md +10 -0
  128. package/library/skills/headless-scripting/SKILL.md +13 -0
  129. package/library/skills/in-game-purchases/SKILL.md +11 -0
  130. package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
  131. package/library/skills/make-game/SKILL.md +29 -0
  132. package/library/skills/multiplayer/SKILL.md +12 -0
  133. package/library/skills/multiplayer-movement/SKILL.md +18 -0
  134. package/library/skills/multiplayer-project/SKILL.md +17 -0
  135. package/library/skills/multiplayer-publish/SKILL.md +17 -5
  136. package/library/skills/multiplayer-state/SKILL.md +16 -0
  137. package/library/skills/multiplayer-testing/SKILL.md +15 -0
  138. package/library/skills/music-track/SKILL.md +16 -0
  139. package/library/skills/navigate-summer/SKILL.md +15 -0
  140. package/library/skills/new-project/SKILL.md +11 -0
  141. package/library/skills/organic-model/SKILL.md +24 -0
  142. package/library/skills/pixel-art/SKILL.md +15 -0
  143. package/library/skills/play/SKILL.md +18 -0
  144. package/library/skills/playtesting-a-feature/SKILL.md +23 -0
  145. package/library/skills/procedural-animation/SKILL.md +18 -0
  146. package/library/skills/prop-model/SKILL.md +23 -0
  147. package/library/skills/publish-your-game/SKILL.md +27 -0
  148. package/library/skills/release-readiness/SKILL.md +12 -0
  149. package/library/skills/remote-deploy/SKILL.md +14 -0
  150. package/library/skills/retarget/SKILL.md +20 -0
  151. package/library/skills/running-in-the-cloud/SKILL.md +10 -0
  152. package/library/skills/save-load/SKILL.md +15 -0
  153. package/library/skills/scene-composition/SKILL.md +17 -0
  154. package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
  155. package/library/skills/scene-scripting/SKILL.md +30 -0
  156. package/library/skills/scene-to-level/SKILL.md +22 -0
  157. package/library/skills/skill-test/SKILL.md +10 -0
  158. package/library/skills/skybox-panorama/SKILL.md +20 -0
  159. package/library/skills/sound-effect/SKILL.md +23 -0
  160. package/library/skills/spatial-placement/SKILL.md +30 -0
  161. package/library/skills/sprite-sheet/SKILL.md +23 -0
  162. package/library/skills/store-art/SKILL.md +11 -0
  163. package/library/skills/store-listing/SKILL.md +10 -0
  164. package/library/skills/summer-analytics/SKILL.md +15 -0
  165. package/library/skills/summer-apps/SKILL.md +29 -0
  166. package/library/skills/summer-assets/SKILL.md +31 -1
  167. package/library/skills/summer-assets/resource.yaml +2 -2
  168. package/library/skills/summer-cli/SKILL.md +133 -0
  169. package/library/skills/summer-cli/resource.yaml +31 -0
  170. package/library/skills/summer-friends/SKILL.md +14 -0
  171. package/library/skills/summer-leaderboards/SKILL.md +16 -0
  172. package/library/skills/summer-match-results/SKILL.md +17 -0
  173. package/library/skills/summer-matchmaking/SKILL.md +17 -0
  174. package/library/skills/summer-parties/SKILL.md +14 -0
  175. package/library/skills/summer-player-data/SKILL.md +14 -0
  176. package/library/skills/summer-store/SKILL.md +15 -0
  177. package/library/skills/summer-world-chat/SKILL.md +14 -0
  178. package/library/skills/summer-world-saves/SKILL.md +14 -0
  179. package/library/skills/text-to-motion/SKILL.md +15 -0
  180. package/library/skills/tileable-texture/SKILL.md +18 -0
  181. package/library/skills/trailer-shot/SKILL.md +18 -0
  182. package/library/skills/tune-performance/SKILL.md +26 -0
  183. package/library/skills/ui-basics/SKILL.md +14 -0
  184. package/library/skills/ui-graphics/SKILL.md +17 -0
  185. package/library/skills/use-widget-asset/SKILL.md +14 -0
  186. package/library/skills/using-summer/SKILL.md +26 -0
  187. package/library/skills/using-summer/resource.yaml +2 -1
  188. package/library/skills/vehicle-model/SKILL.md +27 -0
  189. package/library/skills/verifying-scenes/SKILL.md +29 -0
  190. package/library/skills/vfx-dissolve/SKILL.md +19 -0
  191. package/library/skills/vfx-fire/SKILL.md +26 -0
  192. package/library/skills/vfx-hit-spark/SKILL.md +23 -0
  193. package/library/skills/vfx-lightning/SKILL.md +23 -0
  194. package/library/skills/vfx-magic-glow/SKILL.md +23 -0
  195. package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
  196. package/library/skills/vfx-smoke/SKILL.md +26 -0
  197. package/library/skills/vfx-water-ripple/SKILL.md +23 -0
  198. package/library/skills/voice-line/SKILL.md +16 -0
  199. package/library/skills/web-and-touch-input/SKILL.md +19 -0
  200. package/library/skills/web-compatibility-look/SKILL.md +20 -0
  201. package/library/skills/world-building-3d/SKILL.md +28 -0
  202. package/package.json +3 -2
  203. package/registry/generated/counts.json +2 -2
  204. package/registry/generated/gemini-extension.json +3 -2
  205. package/registry/generated/index.json +157 -118
  206. package/registry/generated/marketplace.claude.json +2 -2
  207. package/registry/generated/plugin.claude-directory.json +168 -0
  208. package/registry/generated/plugin.claude.json +3 -2
  209. package/registry/generated/plugin.codex.json +15 -11
  210. package/registry/generated/plugin.cursor.json +5 -3
  211. package/registry/generated/plugin.factory.json +3 -2
  212. package/registry/generated/server.json +11 -2
  213. package/registry/generated/skills-index.md +5 -4
  214. package/registry/generated/skills-registry.json +16 -2
  215. package/registry/operations/legacy-names.json +35 -0
  216. package/registry/operations/public-catalog.json +33160 -0
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
15
15
  import { parse as parseYaml } from "yaml";
16
- import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
16
+ import { agentSpec, allAgentSpecs, cliSetupRefusal, defaultPathContext, resolveSkillPath } from "./agent-table.js";
17
17
  import { join, sep } from "node:path";
18
18
  import { PACKAGE_ROOT } from "../core/package-root.js";
19
19
  import { configureAgentMcp, parseAgent, SUMMER_MCP_SERVER_NAME, topLevelKey, } from "./agent-config.js";
@@ -219,11 +219,28 @@ export function readSkillMarker(dir) {
219
219
  return null;
220
220
  }
221
221
  }
222
- export function writeSkillMarker(dir, version, now = new Date()) {
222
+ /** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
223
+ export function readKeptSkills(dir) {
224
+ try {
225
+ const kept = JSON.parse(readFileSync(join(dir, SKILL_VERSION_MARKER_FILENAME), "utf-8")).kept;
226
+ return Array.isArray(kept) ? kept.filter((name) => typeof name === "string") : [];
227
+ }
228
+ catch {
229
+ return [];
230
+ }
231
+ }
232
+ /**
233
+ * Write the folder's marker. The skills an upgrade refresh left stay listed
234
+ * (so later refreshes keep leaving them), except the ones this install just
235
+ * wrote: `installed` names them; leave it out to keep every one.
236
+ */
237
+ export function writeSkillMarker(dir, version, now = new Date(), installed) {
223
238
  const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
239
+ const kept = readKeptSkills(dir).filter((name) => !installed?.includes(name));
224
240
  const payload = {
225
241
  version,
226
242
  installedAt: now.toISOString(),
243
+ ...(kept.length > 0 ? { kept } : {}),
227
244
  };
228
245
  writeFileSync(path, JSON.stringify(payload, null, 2) + "\n", "utf-8");
229
246
  return path;
@@ -378,9 +395,16 @@ async function resolveRecordedInstall(resolver, agent) {
378
395
  * The command that refreshes an agent's skills without changing how its MCP
379
396
  * entry was recorded: the local-dev form re-links the same checkout
380
397
  * (`setup --local-dev --force` re-copies the checkout's skills); the npx form
381
- * is for installs that already run the published package.
398
+ * is for installs that already run the published package. An agent with no
399
+ * summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
382
400
  */
383
401
  export function skillsRefreshCommand(agent, install, channel = "latest") {
402
+ if (install?.cliOnly) {
403
+ // No MCP entry to keep: --local-dev and --channel shape only that entry.
404
+ return install.localDev && install.cliPath
405
+ ? `node ${install.cliPath} setup ${agent} --cli --yes --force`
406
+ : `npx clear-npx-cache && npx -y summer-engine@${channel} setup ${agent} --cli --yes --force`;
407
+ }
384
408
  if (install?.localDev && install.cliPath) {
385
409
  return `node ${install.cliPath} setup ${agent} --local-dev --yes --force`;
386
410
  }
@@ -473,6 +497,37 @@ export function readRecordedMcpServer(text, format) {
473
497
  }
474
498
  return null;
475
499
  }
500
+ /**
501
+ * Does the agent's config list a summer-engine server in any shape: the stdio
502
+ * entry setup writes, a URL entry under that name, or (Claude Code) a
503
+ * per-project entry under projects.<dir>.mcpServers?
504
+ */
505
+ export function listsSummerEngine(text, format) {
506
+ if (readRecordedMcpServer(text, format))
507
+ return true;
508
+ if (!text.trim())
509
+ return false;
510
+ if (format === "toml")
511
+ return new RegExp(`^\\s*\\[mcp_servers\\.(?:"${SUMMER_MCP_SERVER_NAME}"|${SUMMER_MCP_SERVER_NAME})[\\].]`, "m").test(text);
512
+ if (format === "toml-array")
513
+ return new RegExp(`^\\s*name\\s*=\\s*"${SUMMER_MCP_SERVER_NAME}"`, "m").test(text);
514
+ let parsed;
515
+ try {
516
+ parsed = format === "yaml-goose" || format === "yaml-hermes" ? parseYaml(text) : JSON.parse(text);
517
+ }
518
+ catch {
519
+ return false;
520
+ }
521
+ if (!parsed || typeof parsed !== "object")
522
+ return false;
523
+ const root = parsed;
524
+ const projects = root.projects && typeof root.projects === "object" ? Object.values(root.projects) : [];
525
+ const maps = [
526
+ ...[topLevelKey(format), "mcpServers", "servers", "mcp", "context_servers", "extensions", "mcp_servers", "amp.mcpServers"].map((key) => root[key]),
527
+ ...projects.map((project) => (project && typeof project === "object" ? project.mcpServers : undefined)),
528
+ ];
529
+ return maps.some((map) => Boolean(map) && typeof map === "object" && SUMMER_MCP_SERVER_NAME in map);
530
+ }
476
531
  /** Is the CLI running this check a source checkout rather than an installed
477
532
  * package? npm/npx/global installs all live under a node_modules directory. */
478
533
  export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
@@ -488,16 +543,17 @@ export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
488
543
  */
489
544
  export async function detectRecordedInstall(agent) {
490
545
  const supported = parseAgent(agent);
546
+ let cliOnly = false;
491
547
  if (supported) {
492
548
  try {
493
549
  const target = await configureAgentMcp({ agent: supported, scope: "user", print: true });
494
- if (existsSync(target.path)) {
495
- const recorded = readRecordedMcpServer(readFileSync(target.path, "utf-8"), target.format);
496
- if (recorded) {
497
- const localDev = isLocalDevServerConfig(recorded);
498
- return { localDev, cliPath: localDev ? recorded.args[0] ?? null : null, source: "agent-config" };
499
- }
550
+ const text = existsSync(target.path) ? readFileSync(target.path, "utf-8") : "";
551
+ const recorded = readRecordedMcpServer(text, target.format);
552
+ if (recorded) {
553
+ const localDev = isLocalDevServerConfig(recorded);
554
+ return { localDev, cliPath: localDev ? recorded.args[0] ?? null : null, source: "agent-config" };
500
555
  }
556
+ cliOnly = !listsSummerEngine(text, target.format) && cliSetupRefusal(agentSpec(supported)) === null;
501
557
  }
502
558
  catch {
503
559
  // Unreadable config: fall through to the running-CLI answer.
@@ -508,6 +564,7 @@ export async function detectRecordedInstall(agent) {
508
564
  localDev: checkout,
509
565
  cliPath: checkout ? [PACKAGE_ROOT, "dist", "bin", "summer.js"].join(sep) : null,
510
566
  source: "running-cli",
567
+ ...(cliOnly ? { cliOnly } : {}),
511
568
  };
512
569
  }
513
570
  export function buildBootDriftNotice(installedVersion, registry, agent) {
@@ -1,7 +1,7 @@
1
1
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { z } from "zod";
3
3
  import { localEntries, localInputSchema, localWireForm, missingInput } from "../core/ops/catalog.js";
4
- import { answerOfHosted, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
4
+ import { answerOfHosted, describeFromShipped, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
5
5
  import { errorAnswer, isLocalOp, legacyNames, localToolsFor, mcpNameOf, resolveOpName } from "../core/ops/contract.js";
6
6
  import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, renderOpRefs, splitToolArgs } from "../core/ops/engine.js";
7
7
  import { answerOfEngineResult, engineFailure, textsOf } from "../core/ops/engine-answer.js";
@@ -343,12 +343,20 @@ export function applyOpsSurface(server, options) {
343
343
  limit: z.number().int().min(1).max(20).optional().describe("Most results (default 5)."),
344
344
  }, { title: "Find a Summer operation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, (async ({ query, area, limit }) => toolResult(await search(query, area, limit ?? 5))));
345
345
  server.tool(SUMMER_CALL, "Run any Summer operation by name (from summer_search) with its input. Engine operations run here; cloud ones run on Summer's server with the same approvals: money, publishing and live changes answer needs_approval with a link. 3.x tool names work too.", {
346
- op: z.string().min(1).max(80).describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
346
+ op: z.string().min(1).max(80).optional().describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
347
+ // Agents reach for `name` (the field summer_search answers with); it means op.
348
+ name: z.string().min(1).max(80).optional().describe("Same as op."),
347
349
  input: z.record(z.unknown()).optional().describe("The operation's input, matching the schema summer_search returned."),
348
350
  dryRun: z.boolean().optional().describe("Check and describe the call without doing it."),
349
351
  idempotencyKey: z.string().min(1).max(128).optional().describe("Retry key for writes; reuse it only to retry the identical call."),
350
- }, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, input, dryRun, idempotencyKey }, extra) => {
351
- const name = op.trim();
352
+ }, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, name: opAlias, input, dryRun, idempotencyKey }, extra) => {
353
+ const name = (op ?? opAlias ?? "").trim();
354
+ if (!name) {
355
+ return toolResult(errorAnswer("invalid_input", "Pass op: the operation name from summer_search, e.g. engine.add_node.", { next: { op: "ops.search" } }));
356
+ }
357
+ if (op !== undefined && opAlias !== undefined && op.trim() !== opAlias.trim()) {
358
+ return toolResult(errorAnswer("invalid_input", `op ("${op}") and name ("${opAlias}") differ; pass one operation name.`, { next: { op: "ops.search" }, details: { path: "name" } }));
359
+ }
352
360
  const args = input ?? {};
353
361
  // A split operation's own tool name, or a mixed 3.x tool whose action picks the operation.
354
362
  const action = typeof args.action === "string" ? args.action : typeof args.mode === "string" ? args.mode : undefined;
@@ -378,13 +386,15 @@ export function applyOpsSurface(server, options) {
378
386
  // `out` stays here: this package saves the files (core/ops/save-files.ts).
379
387
  const { input: sent, out } = options.files.take(resolved, args);
380
388
  let raw;
389
+ // Describing a cloud operation works signed out: the shipped catalog has it.
390
+ const describe = (answer) => (resolved === "ops.describe" && typeof args.name === "string" ? describeFromShipped(args.name, answer) : null) ?? answer;
381
391
  try {
382
392
  raw = await options.cloud.call(SUMMER_CALL, { op: name, ...(input ? { input: sent } : {}), ...(dryRun ? { dryRun } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) });
383
393
  }
384
394
  catch (error) {
385
- return toolResult(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey }));
395
+ return toolResult(describe(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey })));
386
396
  }
387
- return toolResult(await options.files.finish(resolved, answerOfHosted(raw), out));
397
+ return toolResult(describe(await options.files.finish(resolved, answerOfHosted(raw), out)));
388
398
  }));
389
399
  }
390
400
  /** A split operation's input shape: its fields from the 3.x tool's shape, the required ones required. */
@@ -38,6 +38,8 @@ export interface CreateMcpServerOptions {
38
38
  cloud?: CloudCaller;
39
39
  /** v4 only: list every local 3.x name too (the legacy toolset, legacy-toolset.ts). */
40
40
  legacy?: boolean;
41
+ /** One line for this session only, after the instructions (e.g. the installed skills were refreshed). */
42
+ notice?: string | null;
41
43
  }
42
44
  export declare function createMcpServer(selection?: ToolSelection, options?: CreateMcpServerOptions): {
43
45
  server: McpServer;
@@ -45,6 +47,14 @@ export declare function createMcpServer(selection?: ToolSelection, options?: Cre
45
47
  /** v4: saves generated files on this machine (the mounted hosted tools share it). */
46
48
  files: FileSaver;
47
49
  };
50
+ /**
51
+ * First v4 start after an upgrade: bring the installed Summer skills to this
52
+ * version before the legacy decision, which stays on while they are 3.x (and
53
+ * whenever the config asks for it). Not on the v3 surface, whose people chose
54
+ * the 3.x tools, and not under summer doctor's boot probe, whose session would
55
+ * swallow the one notice. A fault here never stops the server.
56
+ */
57
+ export declare function refreshSkillsAfterUpgrade(surface: SurfaceVersion): string | null;
48
58
  export interface StartMcpServerOptions {
49
59
  projectPath?: string;
50
60
  instanceId?: string;
@@ -31,12 +31,14 @@ import { defaultHostedMountDependencies, prepareHostedMount } from "./hosted-mou
31
31
  import { applyOpsSurface, hostedFileTools, OPS_MCP_ASSETS_INSTRUCTIONS, OPS_MCP_INSTRUCTIONS, SUMMER_CALL, SUMMER_SEARCH } from "./ops-surface.js";
32
32
  import { FileSaver } from "../core/ops/save-files.js";
33
33
  import { opForToolCall } from "../core/ops/contract.js";
34
+ import { knownOp } from "../core/ops/catalog.js";
34
35
  import { v4HostedUrl } from "../core/ops/cloud.js";
35
36
  import { serveProviderSafeToolSchemas } from "./provider-safe-schema.js";
36
37
  import { legacyHostedDescription, resolveLegacy, takeLegacyToolset } from "./legacy-toolset.js";
38
+ import { refreshInstalledSkills, refreshNotice } from "../installer/skills-refresh.js";
37
39
  import { OUTPUT_CAPS, PROFILE_TOOL_LIMIT, PROFILES, TOOLSETS, annotationsFor, registryAuthorities, resolveToolSelection, selectionIncludes, withMaxCharactersArg, } from "./tool-surface.js";
38
40
  import { shapeToolResult, singleEditCounter } from "./tool-results.js";
39
- import { recordLocalToolCall, recordMcpSession } from "../core/telemetry.js";
41
+ import { recordLocalToolCall, recordMcpSession, toolErrorCode } from "../core/telemetry.js";
40
42
  import { currentToolContext, registerMcpClientSource, runWithToolContext } from "../core/agent-host.js";
41
43
  import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../installer/version-check.js";
42
44
  import { TOOLKIT_VERSION as version } from "../core/version.js";
@@ -233,9 +235,9 @@ function installToolCallWrapper(server, surfaceMeta) {
233
235
  // A tool another tool runs (summer_call, a v4 operation over its 3.x
234
236
  // tool) is part of that call: only the outer call sends a usage line.
235
237
  const nested = currentToolContext() !== undefined;
236
- const recordUsage = (ok, durationMs) => {
238
+ const recordUsage = (ok, durationMs, error) => {
237
239
  if (!nested)
238
- recordLocalToolCall({ tool: name, op: opForToolCall(name, handlerArgs[0]), ...surfaceMeta, ok, durationMs, mcpClient: hostApp() });
240
+ recordLocalToolCall({ tool: name, op: knownOp(opForToolCall(name, handlerArgs[0])), ...surfaceMeta, ok, durationMs, mcpClient: hostApp(), error });
239
241
  };
240
242
  let result;
241
243
  try {
@@ -253,7 +255,7 @@ function installToolCallWrapper(server, surfaceMeta) {
253
255
  exception: error instanceof Error ? error.message : String(error),
254
256
  durationMs,
255
257
  });
256
- recordUsage(false, durationMs);
258
+ recordUsage(false, durationMs, "exception");
257
259
  throw error;
258
260
  }
259
261
  const durationMs = Date.now() - startedAt;
@@ -272,7 +274,7 @@ function installToolCallWrapper(server, surfaceMeta) {
272
274
  }
273
275
  try {
274
276
  recordMcpSession();
275
- recordUsage(!isError, durationMs);
277
+ recordUsage(!isError, durationMs, isError ? toolErrorCode(result, meta?.failureReason ?? meta?.errorClass) : null);
276
278
  // Opt-in local trajectory capture: one JSONL line per tool call when
277
279
  // SUMMER_TRAJECTORY_DIR is set. recordToolCall never throws and is a
278
280
  // no-op when the env var is unset.
@@ -363,9 +365,10 @@ export function createMcpServer(selection = FULL_SELECTION, options = {}) {
363
365
  name: "summer-engine",
364
366
  version,
365
367
  }, {
366
- instructions: surface === "v4"
368
+ instructions: (surface === "v4"
367
369
  ? engine ? OPS_MCP_INSTRUCTIONS : OPS_MCP_ASSETS_INSTRUCTIONS
368
- : engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS,
370
+ : engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS) +
371
+ (options.notice ? ` ${options.notice} Tell the person once.` : ""),
369
372
  });
370
373
  const getWrappedToolCount = installToolCallWrapper(server, { surfaceVersion: surface, profile: selection.profile });
371
374
  // Requests to Summer's servers name the agent app from the initialize handshake.
@@ -426,6 +429,32 @@ function hostedToolsLabel(toolsets) {
426
429
  return "Tool Factory tools (summer_tool_*) and asset editing tools";
427
430
  return undefined;
428
431
  }
432
+ /**
433
+ * First v4 start after an upgrade: bring the installed Summer skills to this
434
+ * version before the legacy decision, which stays on while they are 3.x (and
435
+ * whenever the config asks for it). Not on the v3 surface, whose people chose
436
+ * the 3.x tools, and not under summer doctor's boot probe, whose session would
437
+ * swallow the one notice. A fault here never stops the server.
438
+ */
439
+ export function refreshSkillsAfterUpgrade(surface) {
440
+ if (surface !== "v4" || process.env.SUMMER_MCP_DOCTOR === "1")
441
+ return null;
442
+ try {
443
+ const { refreshed, failures } = refreshInstalledSkills(version);
444
+ if (failures.length > 0)
445
+ appendMcpLogEvent("mcp:skills_refresh_failed", { version, failures });
446
+ const notice = refreshNotice(refreshed, failures);
447
+ if (notice) {
448
+ appendMcpLogEvent("mcp:skills_refreshed", { version, notice });
449
+ process.stderr.write(`[summer-mcp] ${notice}\n`);
450
+ }
451
+ return notice;
452
+ }
453
+ catch (error) {
454
+ appendMcpLogEvent("mcp:skills_refresh_failed", { version, ...errorDetails(error) });
455
+ return null;
456
+ }
457
+ }
429
458
  export async function startMcpServer(options = {}) {
430
459
  configureMcpEngineSelection({
431
460
  instanceId: options.instanceId ?? process.env.SUMMER_ENGINE_INSTANCE_ID,
@@ -445,6 +474,7 @@ export async function startMcpServer(options = {}) {
445
474
  },
446
475
  });
447
476
  const surface = resolveSurface(options.surface);
477
+ const skillsNotice = refreshSkillsAfterUpgrade(surface);
448
478
  // `legacy` is not a toolset of its own: it lists the 3.x names on the v4 surface.
449
479
  const toolsetList = takeLegacyToolset(options.toolsets ?? process.env.SUMMER_TOOLSETS);
450
480
  const legacy = surface === "v4" ? resolveLegacy(toolsetList.requested) : { on: false, reason: "off" };
@@ -459,7 +489,7 @@ export async function startMcpServer(options = {}) {
459
489
  // v4 runs cloud operations through the hosted mount below, once it exists.
460
490
  let hostedCall = null;
461
491
  const cloud = { call: (name, args) => (hostedCall ? hostedCall(name, args) : NO_CLOUD.call(name, args)) };
462
- const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on });
492
+ const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on, notice: skillsNotice });
463
493
  // Fire-and-forget — never block tool registration on the npm registry.
464
494
  void probeBootDrift().catch((error) => {
465
495
  setCachedBootDriftNotice(null);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * AGENTS.md and CLAUDE.md in a Summer project. AGENTS.md is the one file
3
+ * Codex, Cursor, Copilot, Amp and other agents read in every session;
4
+ * Claude Code reads CLAUDE.md, which here only imports AGENTS.md.
5
+ * `summer create` writes both; `summer setup --yes` inside a project adds
6
+ * them when missing.
7
+ *
8
+ * An existing file is never overwritten: Summer adds one marked block once,
9
+ * and later runs replace only that block. A link is followed only to a file
10
+ * inside the project, and a CLAUDE.md that is the same file as AGENTS.md
11
+ * gets the AGENTS.md block only. The five commands are the list in the
12
+ * summer-cli skill (between its project-commands markers), so the two
13
+ * cannot drift; the skill's test runs every command both of them teach.
14
+ */
15
+ export declare const SUMMER_BLOCK_START = "<!-- summer-engine:start. summer setup updates only this block; write your own notes outside it. -->";
16
+ export declare const SUMMER_BLOCK_END = "<!-- summer-engine:end -->";
17
+ /** The numbered command list from the summer-cli skill. */
18
+ export declare function projectCommands(skill?: string): string;
19
+ /** The Summer block of AGENTS.md, markers included. */
20
+ export declare function renderAgentsBlock(commands?: string): string;
21
+ export type InstructionAction = "created" | "added" | "updated" | "unchanged" | "skipped";
22
+ export interface InstructionFileResult {
23
+ file: "AGENTS.md" | "CLAUDE.md";
24
+ path: string;
25
+ action: InstructionAction;
26
+ /** Why a file was skipped or left as it is. */
27
+ reason?: string;
28
+ }
29
+ /**
30
+ * Write or update AGENTS.md and CLAUDE.md in the project root. A new
31
+ * AGENTS.md is the Summer block; a new CLAUDE.md is `@AGENTS.md` only. An
32
+ * existing CLAUDE.md that already imports AGENTS.md is left as it is. Each
33
+ * file is its own step: a file that cannot be read or written is skipped
34
+ * with the reason, and the other is still done.
35
+ */
36
+ export declare function writeAgentInstructions(projectDir: string, options?: {
37
+ dryRun?: boolean;
38
+ block?: string;
39
+ }): InstructionFileResult[];
40
+ /**
41
+ * The Summer project the working directory is in, for `summer setup`: the
42
+ * nearest folder with project.godot, looking up no further than the git
43
+ * top level, and never the home folder or the filesystem root (a stray
44
+ * project.godot there must not put an AGENTS.md or CLAUDE.md in every
45
+ * session). Null when there is none.
46
+ */
47
+ export declare function findSetupProjectRoot(cwd?: string): string | null;
@@ -0,0 +1,226 @@
1
+ import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { basename, dirname, join, parse, relative, resolve, isAbsolute } from "path";
4
+ import { PACKAGE_ROOT } from "../core/package-root.js";
5
+ /**
6
+ * AGENTS.md and CLAUDE.md in a Summer project. AGENTS.md is the one file
7
+ * Codex, Cursor, Copilot, Amp and other agents read in every session;
8
+ * Claude Code reads CLAUDE.md, which here only imports AGENTS.md.
9
+ * `summer create` writes both; `summer setup --yes` inside a project adds
10
+ * them when missing.
11
+ *
12
+ * An existing file is never overwritten: Summer adds one marked block once,
13
+ * and later runs replace only that block. A link is followed only to a file
14
+ * inside the project, and a CLAUDE.md that is the same file as AGENTS.md
15
+ * gets the AGENTS.md block only. The five commands are the list in the
16
+ * summer-cli skill (between its project-commands markers), so the two
17
+ * cannot drift; the skill's test runs every command both of them teach.
18
+ */
19
+ export const SUMMER_BLOCK_START = "<!-- summer-engine:start. summer setup updates only this block; write your own notes outside it. -->";
20
+ export const SUMMER_BLOCK_END = "<!-- summer-engine:end -->";
21
+ const CLAUDE_IMPORTS = new Set(["@AGENTS.md", "@./AGENTS.md"]);
22
+ const SKILL_PATH = ["library", "skills", "summer-cli", "SKILL.md"];
23
+ const COMMANDS = /<!-- project-commands[^>]*-->\n([\s\S]*?)\n<!-- \/project-commands -->/;
24
+ /** The numbered command list from the summer-cli skill. */
25
+ export function projectCommands(skill = readFileSync(join(PACKAGE_ROOT, ...SKILL_PATH), "utf-8")) {
26
+ const match = COMMANDS.exec(skill);
27
+ if (!match)
28
+ throw new Error("The summer-cli skill has no project-commands list.");
29
+ return match[1].trim();
30
+ }
31
+ /** The Summer block of AGENTS.md, markers included. */
32
+ export function renderAgentsBlock(commands = projectCommands()) {
33
+ return [
34
+ SUMMER_BLOCK_START,
35
+ "# Summer Engine game",
36
+ "",
37
+ "This is a game made with Summer Engine, the game engine built for AI. Game code is GDScript (`.gd`), scenes are `.tscn`, resources are `.tres`, and `project.godot` is the project file.",
38
+ "",
39
+ "## How to work on it",
40
+ "",
41
+ "- Change scenes through Summer while Summer Engine has the project open. The editor rewrites `.tscn` files when it saves, so hand edits get lost.",
42
+ "- With Summer's MCP server, call the `summer_*` tools and start with `summer_get_project_context`. Without it, run the same operations as `summer` commands in the shell (the `summer-cli` skill). If `summer` is missing or older than 4.0, use `npx -y summer-engine@latest` in its place.",
43
+ "- Summer's skills teach each part of a game. `npx -y summer-engine@latest setup <agent> --yes` installs them for your agent (add `--cli` for no MCP server). Start with `using-summer`, and find the right one with `summer docs search --query \"<the task>\"`.",
44
+ "- `.summer/` is the project's memory: `GameSoul.md` says what the game is, `memory/` holds the decisions to keep. Read them before you change the game's look, sound or story.",
45
+ "- Check your own work: script errors, a screenshot, a play run. Ask the person to play only for feel and fun.",
46
+ "",
47
+ "## The five commands",
48
+ "",
49
+ commands,
50
+ "",
51
+ "## Publishing",
52
+ "",
53
+ "The `publish-your-game` skill has the steps; `summer ops search \"publish my game\"` finds each operation. `summer store publish` sends the game to Summer's store review, and the game's owner approves it: give them the link it prints. Nothing goes live until they approve.",
54
+ SUMMER_BLOCK_END,
55
+ ].join("\n");
56
+ }
57
+ /** The text with `block` as its Summer block: replaced where it is, else appended. Keeps the file's line endings. */
58
+ function withBlock(text, block) {
59
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
60
+ const ours = block.replace(/\r?\n/g, eol);
61
+ const start = text.indexOf(SUMMER_BLOCK_START);
62
+ if (start === -1) {
63
+ // Only trailing line breaks change outside the block; an empty file is the block alone.
64
+ return { text: text.trim() ? `${text.replace(/(\r?\n)*$/, eol)}${eol}${ours}${eol}` : `${ours}${eol}`, action: "added" };
65
+ }
66
+ const end = text.indexOf(SUMMER_BLOCK_END, start);
67
+ if (end === -1)
68
+ return { text, action: "skipped", reason: "its Summer block has no end marker" };
69
+ // Two blocks (a merge, or the markers quoted in the person's text): which
70
+ // one is Summer's is not knowable, so the person picks; nothing is deleted.
71
+ if (text.indexOf(SUMMER_BLOCK_START, end) !== -1)
72
+ return { text, action: "skipped", reason: "it has two Summer blocks; delete one and run setup again" };
73
+ const next = `${text.slice(0, start)}${ours}${text.slice(end + SUMMER_BLOCK_END.length)}`;
74
+ return { text: next, action: next === text ? "unchanged" : "updated" };
75
+ }
76
+ /** Does the text import AGENTS.md on a line of its own, outside code fences? */
77
+ function importsAgents(text) {
78
+ let fence = false;
79
+ for (const line of text.split(/\r?\n/)) {
80
+ const trimmed = line.trim();
81
+ if (/^(```|~~~)/.test(trimmed))
82
+ fence = !fence;
83
+ else if (!fence && CLAUDE_IMPORTS.has(trimmed))
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+ function inside(root, path) {
89
+ const rel = relative(root, path);
90
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
91
+ }
92
+ /**
93
+ * Where a file may be written, and `real`, the file a write lands in:
94
+ * "missing" (create it: here, or the missing target of a link inside the
95
+ * project), "file" (a file, or a link to one inside the project), or a
96
+ * reason to leave it alone (a link that points nowhere or out of the
97
+ * project). `real` is known before anything is written, so a link from one
98
+ * name to the other counts as one file even when its target is missing.
99
+ */
100
+ function locate(path, realRoot) {
101
+ let isLink = false;
102
+ try {
103
+ isLink = lstatSync(path).isSymbolicLink();
104
+ }
105
+ catch {
106
+ return { kind: "missing", real: join(realRoot, basename(path)) };
107
+ }
108
+ let real;
109
+ try {
110
+ real = realpathSync(path);
111
+ }
112
+ catch {
113
+ const target = resolve(realRoot, readlinkSync(path));
114
+ return inside(realRoot, target) ? { kind: "missing", real: target } : { kind: "skip", reason: "it is a link to a file that does not exist" };
115
+ }
116
+ if (isLink && !inside(realRoot, real))
117
+ return { kind: "skip", reason: "it is a link to a file outside the project" };
118
+ return { kind: "file", real };
119
+ }
120
+ /**
121
+ * Write or update AGENTS.md and CLAUDE.md in the project root. A new
122
+ * AGENTS.md is the Summer block; a new CLAUDE.md is `@AGENTS.md` only. An
123
+ * existing CLAUDE.md that already imports AGENTS.md is left as it is. Each
124
+ * file is its own step: a file that cannot be read or written is skipped
125
+ * with the reason, and the other is still done.
126
+ */
127
+ export function writeAgentInstructions(projectDir, options = {}) {
128
+ const agents = join(projectDir, "AGENTS.md");
129
+ const claude = join(projectDir, "CLAUDE.md");
130
+ let block;
131
+ let realRoot;
132
+ try {
133
+ block = options.block ?? renderAgentsBlock();
134
+ realRoot = realpathSync(projectDir);
135
+ }
136
+ catch (error) {
137
+ const reason = messageOf(error);
138
+ return [
139
+ { file: "AGENTS.md", path: agents, action: "skipped", reason },
140
+ { file: "CLAUDE.md", path: claude, action: "skipped", reason },
141
+ ];
142
+ }
143
+ const write = (path, text) => {
144
+ if (!options.dryRun)
145
+ writeFileSync(path, text, "utf-8");
146
+ };
147
+ const step = (file, path, run) => {
148
+ try {
149
+ return { file, path, ...run() };
150
+ }
151
+ catch (error) {
152
+ return { file, path, action: "skipped", reason: messageOf(error) };
153
+ }
154
+ };
155
+ const agentsAt = locate(agents, realRoot);
156
+ const agentsResult = step("AGENTS.md", agents, () => {
157
+ if (agentsAt.kind === "skip")
158
+ return { action: "skipped", reason: agentsAt.reason };
159
+ if (agentsAt.kind === "missing") {
160
+ write(agents, `${block}\n`);
161
+ return { action: "created" };
162
+ }
163
+ const next = withBlock(readFileSync(agents, "utf-8"), block);
164
+ if (next.action === "added" || next.action === "updated")
165
+ write(agents, next.text);
166
+ return { action: next.action, ...(next.reason ? { reason: next.reason } : {}) };
167
+ });
168
+ const claudeAt = locate(claude, realRoot);
169
+ const claudeResult = step("CLAUDE.md", claude, () => {
170
+ if (claudeAt.kind === "skip")
171
+ return { action: "skipped", reason: claudeAt.reason };
172
+ // CLAUDE.md -> AGENTS.md (or the reverse): one file, which already has the AGENTS.md block.
173
+ if (agentsAt.kind !== "skip" && claudeAt.real === agentsAt.real) {
174
+ return { action: "unchanged", reason: "it is the same file as AGENTS.md" };
175
+ }
176
+ if (claudeAt.kind === "missing") {
177
+ write(claude, "@AGENTS.md\n");
178
+ return { action: "created" };
179
+ }
180
+ const text = readFileSync(claude, "utf-8");
181
+ if (importsAgents(text) && !text.includes(SUMMER_BLOCK_START))
182
+ return { action: "unchanged" };
183
+ const next = withBlock(text, `${SUMMER_BLOCK_START}\n@AGENTS.md\n${SUMMER_BLOCK_END}`);
184
+ if (next.action === "added" || next.action === "updated")
185
+ write(claude, next.text);
186
+ return { action: next.action, ...(next.reason ? { reason: next.reason } : {}) };
187
+ });
188
+ return [agentsResult, claudeResult];
189
+ }
190
+ /**
191
+ * The Summer project the working directory is in, for `summer setup`: the
192
+ * nearest folder with project.godot, looking up no further than the git
193
+ * top level, and never the home folder or the filesystem root (a stray
194
+ * project.godot there must not put an AGENTS.md or CLAUDE.md in every
195
+ * session). Null when there is none.
196
+ */
197
+ export function findSetupProjectRoot(cwd = process.cwd()) {
198
+ let current;
199
+ try {
200
+ current = realpathSync(resolve(cwd));
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ let home;
206
+ try {
207
+ home = realpathSync(homedir());
208
+ }
209
+ catch {
210
+ home = homedir();
211
+ }
212
+ while (current !== home && current !== parse(current).root) {
213
+ if (existsSync(join(current, "project.godot")))
214
+ return current;
215
+ if (existsSync(join(current, ".git")))
216
+ return null;
217
+ const parent = dirname(current);
218
+ if (parent === current)
219
+ return null;
220
+ current = parent;
221
+ }
222
+ return null;
223
+ }
224
+ function messageOf(error) {
225
+ return error instanceof Error ? error.message : String(error);
226
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/gemini — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "version": "4.0.0",
5
- "description": "Agent tooling for Summer Engine: MCP bridge, context primer, and game-dev skills. Use with the npm CLI for skill files on disk.",
4
+ "version": "4.3.0",
5
+ "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. The Summer Engine MCP server, context primer and game-dev skills.",
6
6
  "contextFileName": "GEMINI.md",
7
7
  "mcpServers": {
8
8
  "summer-engine": {
@@ -108,6 +108,7 @@
108
108
  "./library/skills/summer-apps/",
109
109
  "./library/skills/summer-apps-design/",
110
110
  "./library/skills/summer-assets/",
111
+ "./library/skills/summer-cli/",
111
112
  "./library/skills/summer-friends/",
112
113
  "./library/skills/summer-leaderboards/",
113
114
  "./library/skills/summer-match-results/",
@@ -58,7 +58,7 @@ are listed where they differ.
58
58
  | claude-desktop | — | MCP: `~/Library/Application Support/Claude/claude_desktop_config.json`; Linux `~/.config/Claude/claude_desktop_config.json`; Windows `%APPDATA%/Claude/claude_desktop_config.json`; no skills folder (MCP only) |
59
59
  | codex | `.codex-plugin/plugin.json` | MCP: `~/.codex/config.toml`; project `.codex/config.toml`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
60
60
  | cursor | `.cursor-plugin/plugin.json` | MCP: `~/.cursor/mcp.json`; project `.cursor/mcp.json`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
61
- | windsurf | — | MCP: `~/.codeium/windsurf/mcp_config.json`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
61
+ | windsurf | — | MCP: `~/.config/devin/mcp_config.json`; Windows `%APPDATA%/devin/mcp_config.json`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
62
62
  | antigravity | — | MCP: `~/.gemini/config/mcp_config.json`; project `.agents/mcp_config.json`; skills: `~/.gemini/config/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
63
63
  | gemini (legacy) | `gemini-extension.json` | MCP: `~/.gemini/extensions/summer-engine/gemini-extension.json`; skills: `~/.gemini/extensions/summer-engine/skills` (`<skill>/SKILL.md`) |
64
64
  | cline | — | MCP: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; Linux `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; Windows `%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; skills: `~/.cline/skills`; project `.cline/skills` (`<skill>/SKILL.md`) |
@@ -8,6 +8,12 @@ Generated by `scripts/generate-registry` from `library/**/resource.yaml`:
8
8
  - `registry/generated/marketplace.claude.json` -> `.claude-plugin/marketplace.json`
9
9
  (marketplace listing wrapper).
10
10
 
11
+ - `registry/generated/plugin.claude-directory.json` -> `plugins/claude/.claude-plugin/plugin.json`,
12
+ plus the rest of `plugins/claude/` (`scripts/generate-registry/mirrors.ts`): the same
13
+ plugin for Anthropic's plugin directory, from a folder of its own (under 512 files,
14
+ no symlinks), with the server and every `summer-engine@latest` pinned to this
15
+ version. Submit `plugins/claude` as the plugin path.
16
+
11
17
  The MCP pointer `./.mcp.json` these manifests reference is generated as
12
18
  well (`registry/generated/mcp.json -> .mcp.json`, owned by the claude target).
13
19
 
@@ -11,6 +11,10 @@
11
11
  {
12
12
  "generated": "mcp.json",
13
13
  "destination": ".mcp.json"
14
+ },
15
+ {
16
+ "generated": "plugin.claude-directory.json",
17
+ "destination": "plugins/claude/.claude-plugin/plugin.json"
14
18
  }
15
19
  ]
16
20
  }