summer-engine 3.8.0 → 4.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 (485) hide show
  1. package/.claude-plugin/marketplace.json +2 -13
  2. package/.claude-plugin/plugin.json +23 -22
  3. package/.codex-plugin/plugin.json +4 -12
  4. package/.cursor-plugin/plugin.json +4 -12
  5. package/.factory-plugin/plugin.json +4 -12
  6. package/AGENTS.md +19 -12
  7. package/CHANGELOG.md +96 -0
  8. package/GEMINI.md +3 -3
  9. package/README.md +103 -33
  10. package/assets/icon.png +0 -0
  11. package/commands/start.md +71 -0
  12. package/commands/summer.md +3 -2
  13. package/dist/cli/banner.js +3 -0
  14. package/dist/cli/commands/login.d.ts +27 -0
  15. package/dist/cli/commands/login.js +58 -12
  16. package/dist/cli/commands/ops.d.ts +47 -0
  17. package/dist/cli/commands/ops.js +301 -0
  18. package/dist/cli/commands/setup.d.ts +2 -0
  19. package/dist/cli/commands/setup.js +37 -12
  20. package/dist/cli/commands/skills.js +11 -7
  21. package/dist/cli/index.js +7 -0
  22. package/dist/core/agent-host.d.ts +65 -0
  23. package/dist/core/agent-host.js +193 -0
  24. package/dist/core/capabilities/agent-playbook.d.ts +3 -0
  25. package/dist/core/capabilities/agent-playbook.js +54 -2
  26. package/dist/core/capabilities/apps.d.ts +25 -2
  27. package/dist/core/capabilities/apps.js +25 -12
  28. package/dist/core/capabilities/doctor.d.ts +3 -0
  29. package/dist/core/capabilities/doctor.js +20 -15
  30. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  31. package/dist/core/capabilities/game-task-plan.d.ts +12 -6
  32. package/dist/core/capabilities/game-task-plan.js +80 -5
  33. package/dist/core/capabilities/motion-generation.js +2 -2
  34. package/dist/core/capabilities/navigation/open.d.ts +6 -2
  35. package/dist/core/capabilities/navigation/open.js +3 -0
  36. package/dist/core/capabilities/publish-build.js +2 -0
  37. package/dist/core/capabilities/runtime-control.d.ts +4 -4
  38. package/dist/core/capabilities/scene-script-target.d.ts +17 -0
  39. package/dist/core/capabilities/scene-script-target.js +70 -0
  40. package/dist/core/capabilities/sfx-choices.d.ts +4 -0
  41. package/dist/core/capabilities/sfx-choices.js +13 -6
  42. package/dist/core/capabilities/tool-dispatch.d.ts +12 -3
  43. package/dist/core/capabilities/tool-dispatch.js +81 -46
  44. package/dist/core/embeddings.d.ts +2 -0
  45. package/dist/core/embeddings.js +1 -1
  46. package/dist/core/error-codes.d.ts +62 -0
  47. package/dist/core/error-codes.js +97 -0
  48. package/dist/core/format.d.ts +2 -0
  49. package/dist/core/format.js +2 -1
  50. package/dist/core/hosted-mcp.d.ts +22 -0
  51. package/dist/core/hosted-mcp.js +56 -1
  52. package/dist/core/library-read.js +5 -0
  53. package/dist/core/library-search.js +2 -1
  54. package/dist/core/oauth.js +4 -0
  55. package/dist/core/ops/catalog.d.ts +41 -0
  56. package/dist/core/ops/catalog.js +145 -0
  57. package/dist/core/ops/cloud.d.ts +56 -0
  58. package/dist/core/ops/cloud.js +175 -0
  59. package/dist/core/ops/contract.d.ts +119 -0
  60. package/dist/core/ops/contract.js +142 -0
  61. package/dist/core/ops/engine-answer.d.ts +25 -0
  62. package/dist/core/ops/engine-answer.js +117 -0
  63. package/dist/core/ops/engine.d.ts +55 -0
  64. package/dist/core/ops/engine.js +649 -0
  65. package/dist/core/ops/save-files.d.ts +78 -0
  66. package/dist/core/ops/save-files.js +419 -0
  67. package/dist/core/ops/search.d.ts +9 -0
  68. package/dist/core/ops/search.js +55 -0
  69. package/dist/core/skills-registry.d.ts +16 -1
  70. package/dist/core/skills-registry.js +35 -2
  71. package/dist/core/telemetry.d.ts +32 -0
  72. package/dist/core/telemetry.js +38 -10
  73. package/dist/core/tool-profiles.d.ts +25 -0
  74. package/dist/core/tool-profiles.js +41 -0
  75. package/dist/installer/agent-config.d.ts +17 -4
  76. package/dist/installer/agent-config.js +22 -6
  77. package/dist/installer/agent-table.d.ts +3 -0
  78. package/dist/installer/agent-table.js +10 -7
  79. package/dist/installer/setup.d.ts +2 -0
  80. package/dist/installer/setup.js +9 -4
  81. package/dist/installer/version-check.d.ts +29 -9
  82. package/dist/installer/version-check.js +81 -28
  83. package/dist/mcp/command.js +7 -0
  84. package/dist/mcp/hosted-mount.d.ts +27 -2
  85. package/dist/mcp/hosted-mount.js +53 -23
  86. package/dist/mcp/legacy-toolset.d.ts +27 -0
  87. package/dist/mcp/legacy-toolset.js +43 -0
  88. package/dist/mcp/ops-surface.d.ts +59 -0
  89. package/dist/mcp/ops-surface.js +443 -0
  90. package/dist/mcp/server.d.ts +35 -11
  91. package/dist/mcp/server.js +234 -89
  92. package/dist/mcp/tool-results.d.ts +26 -0
  93. package/dist/mcp/tool-results.js +81 -0
  94. package/dist/mcp/tool-surface.d.ts +65 -0
  95. package/dist/mcp/tool-surface.js +317 -0
  96. package/dist/mcp/tools/app-tools.js +9 -2
  97. package/dist/mcp/tools/asset-tools.js +55 -53
  98. package/dist/mcp/tools/audit-tools.js +21 -21
  99. package/dist/mcp/tools/debug-tools.js +6 -6
  100. package/dist/mcp/tools/fabricate-tools.js +6 -6
  101. package/dist/mcp/tools/generate-tools.js +76 -70
  102. package/dist/mcp/tools/project-tools.d.ts +9 -4
  103. package/dist/mcp/tools/project-tools.js +16 -10
  104. package/dist/mcp/tools/scene-tools.js +12 -23
  105. package/dist/mcp/tools/script-tools.js +9 -5
  106. package/dist/mcp/tools/with-engine.d.ts +6 -0
  107. package/dist/mcp/tools/with-engine.js +19 -0
  108. package/gemini-extension.json +4 -12
  109. package/hooks/pre-commit-doctor.sh +5 -4
  110. package/hooks/session-start.sh +24 -28
  111. package/integrations/claude-desktop/README.md +1 -1
  112. package/integrations/copilot-jetbrains/README.md +1 -1
  113. package/integrations/junie/README.md +1 -1
  114. package/integrations/lm-studio/README.md +1 -1
  115. package/integrations/trae/README.md +1 -1
  116. package/integrations/visual-studio/README.md +1 -1
  117. package/library/references/mcp-tools-reference/mcp-tools-reference.md +79 -51
  118. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  119. package/library/references/product-map/resource.yaml +2 -2
  120. package/library/skills/3d-lighting/SKILL.md +53 -28
  121. package/library/skills/3d-lighting/resource.yaml +1 -1
  122. package/library/skills/README.md +119 -117
  123. package/library/skills/adaptive-music/SKILL.md +19 -23
  124. package/library/skills/adaptive-music/resource.yaml +1 -1
  125. package/library/skills/agent-playtesting/SKILL.md +1 -1
  126. package/library/skills/agent-playtesting/resource.yaml +1 -1
  127. package/library/skills/ai-characters-in-games/SKILL.md +1 -1
  128. package/library/skills/ai-characters-in-games/resource.yaml +1 -1
  129. package/library/skills/ambient-bed/SKILL.md +22 -13
  130. package/library/skills/ambient-bed/resource.yaml +1 -1
  131. package/library/skills/animated-loop/SKILL.md +41 -32
  132. package/library/skills/animated-loop/resource.yaml +1 -1
  133. package/library/skills/animation-tree/SKILL.md +1 -1
  134. package/library/skills/animation-tree/resource.yaml +1 -1
  135. package/library/skills/art-direction/SKILL.md +3 -3
  136. package/library/skills/art-direction/resource.yaml +1 -1
  137. package/library/skills/asset-strategy/SKILL.md +21 -4
  138. package/library/skills/asset-strategy/resource.yaml +2 -1
  139. package/library/skills/audio-direction/SKILL.md +10 -10
  140. package/library/skills/audio-direction/resource.yaml +1 -1
  141. package/library/skills/auto-fire-targeting/SKILL.md +1 -1
  142. package/library/skills/auto-fire-targeting/resource.yaml +1 -1
  143. package/library/skills/bounded-engine-runs/SKILL.md +1 -1
  144. package/library/skills/bounded-engine-runs/resource.yaml +1 -1
  145. package/library/skills/brainstorm-game/SKILL.md +1 -1
  146. package/library/skills/brainstorm-game/resource.yaml +1 -1
  147. package/library/skills/brainstorming/SKILL.md +1 -1
  148. package/library/skills/brainstorming/resource.yaml +1 -1
  149. package/library/skills/browse-templates/SKILL.md +1 -1
  150. package/library/skills/browse-templates/resource.yaml +1 -1
  151. package/library/skills/camera-rigs/SKILL.md +2 -2
  152. package/library/skills/camera-rigs/resource.yaml +1 -1
  153. package/library/skills/celeste-momentum-platforming/resource.yaml +1 -0
  154. package/library/skills/character-animation-wiring/SKILL.md +4 -4
  155. package/library/skills/character-animation-wiring/resource.yaml +1 -1
  156. package/library/skills/character-model/SKILL.md +55 -47
  157. package/library/skills/character-model/resource.yaml +1 -1
  158. package/library/skills/character-movement/SKILL.md +3 -3
  159. package/library/skills/character-movement/resource.yaml +1 -1
  160. package/library/skills/character-portrait/SKILL.md +24 -19
  161. package/library/skills/character-portrait/resource.yaml +1 -1
  162. package/library/skills/cinematic-cutscene/SKILL.md +58 -46
  163. package/library/skills/cinematic-cutscene/resource.yaml +1 -1
  164. package/library/skills/combat-basics/SKILL.md +1 -1
  165. package/library/skills/combat-basics/resource.yaml +1 -1
  166. package/library/skills/compatibility-renderer-traps/SKILL.md +1 -1
  167. package/library/skills/compatibility-renderer-traps/resource.yaml +1 -1
  168. package/library/skills/concept-art/SKILL.md +16 -12
  169. package/library/skills/concept-art/resource.yaml +1 -1
  170. package/library/skills/concept-to-level/SKILL.md +239 -0
  171. package/library/skills/concept-to-level/references/technical-rules.md +150 -0
  172. package/library/skills/concept-to-level/resource.yaml +44 -0
  173. package/library/skills/create-asset-sheet/SKILL.md +19 -13
  174. package/library/skills/create-asset-sheet/resource.yaml +1 -1
  175. package/library/skills/debug/SKILL.md +2 -2
  176. package/library/skills/debug/resource.yaml +1 -1
  177. package/library/skills/debugging-game-feel/SKILL.md +1 -1
  178. package/library/skills/debugging-game-feel/resource.yaml +1 -1
  179. package/library/skills/design-level/SKILL.md +23 -35
  180. package/library/skills/design-level/resource.yaml +1 -1
  181. package/library/skills/design-mechanic/SKILL.md +1 -1
  182. package/library/skills/design-mechanic/resource.yaml +1 -1
  183. package/library/skills/design-npc/SKILL.md +22 -24
  184. package/library/skills/design-npc/resource.yaml +1 -1
  185. package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
  186. package/library/skills/diagnosing-perf-regressions/resource.yaml +1 -1
  187. package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
  188. package/library/skills/driving-the-editor-ui/resource.yaml +1 -1
  189. package/library/skills/environment-kit/SKILL.md +20 -16
  190. package/library/skills/environment-kit/resource.yaml +1 -1
  191. package/library/skills/export-and-ship/SKILL.md +6 -6
  192. package/library/skills/export-and-ship/resource.yaml +1 -1
  193. package/library/skills/fabricating-assets/SKILL.md +7 -7
  194. package/library/skills/fabricating-assets/resource.yaml +3 -3
  195. package/library/skills/facial-and-lipsync/SKILL.md +14 -15
  196. package/library/skills/facial-and-lipsync/resource.yaml +1 -1
  197. package/library/skills/fps-controller/SKILL.md +33 -41
  198. package/library/skills/fps-controller/resource.yaml +1 -1
  199. package/library/skills/game-feel/SKILL.md +8 -8
  200. package/library/skills/game-feel/resource.yaml +1 -1
  201. package/library/skills/gameskill/SKILL.md +4 -4
  202. package/library/skills/gameskill/resource.yaml +1 -1
  203. package/library/skills/gdscript-patterns/SKILL.md +1 -1
  204. package/library/skills/gdscript-patterns/resource.yaml +1 -1
  205. package/library/skills/generate-motion/SKILL.md +30 -21
  206. package/library/skills/generate-motion/resource.yaml +1 -1
  207. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
  208. package/library/skills/godogen-visual-proof-game-generation/resource.yaml +1 -0
  209. package/library/skills/grow-analytics/SKILL.md +3 -3
  210. package/library/skills/grow-analytics/resource.yaml +1 -1
  211. package/library/skills/headless-scripting/SKILL.md +1 -1
  212. package/library/skills/headless-scripting/resource.yaml +1 -1
  213. package/library/skills/in-game-purchases/SKILL.md +1 -1
  214. package/library/skills/in-game-purchases/resource.yaml +1 -1
  215. package/library/skills/instantiate-asset-pack/SKILL.md +2 -2
  216. package/library/skills/instantiate-asset-pack/resource.yaml +1 -1
  217. package/library/skills/lumera-single-image-scene-reconstruction/resource.yaml +1 -0
  218. package/library/skills/make-game/SKILL.md +4 -4
  219. package/library/skills/make-game/references/2d-platformer.md +1 -1
  220. package/library/skills/make-game/references/3d-fps.md +1 -1
  221. package/library/skills/make-game/references/3d-horror.md +1 -1
  222. package/library/skills/make-game/references/3d-third-person.md +2 -2
  223. package/library/skills/make-game/resource.yaml +1 -1
  224. package/library/skills/multiplayer/SKILL.md +1 -1
  225. package/library/skills/multiplayer/resource.yaml +1 -1
  226. package/library/skills/multiplayer-movement/SKILL.md +1 -1
  227. package/library/skills/multiplayer-movement/resource.yaml +1 -1
  228. package/library/skills/multiplayer-project/SKILL.md +1 -1
  229. package/library/skills/multiplayer-project/resource.yaml +1 -1
  230. package/library/skills/multiplayer-publish/SKILL.md +8 -7
  231. package/library/skills/multiplayer-publish/resource.yaml +3 -3
  232. package/library/skills/multiplayer-state/SKILL.md +1 -1
  233. package/library/skills/multiplayer-state/resource.yaml +1 -1
  234. package/library/skills/multiplayer-testing/SKILL.md +1 -1
  235. package/library/skills/multiplayer-testing/resource.yaml +1 -1
  236. package/library/skills/music-track/SKILL.md +22 -13
  237. package/library/skills/music-track/resource.yaml +1 -1
  238. package/library/skills/navigate-summer/SKILL.md +11 -9
  239. package/library/skills/navigate-summer/resource.yaml +1 -1
  240. package/library/skills/new-project/SKILL.md +1 -1
  241. package/library/skills/new-project/resource.yaml +1 -1
  242. package/library/skills/organic-model/SKILL.md +24 -18
  243. package/library/skills/organic-model/resource.yaml +1 -1
  244. package/library/skills/pixel-art/SKILL.md +27 -23
  245. package/library/skills/pixel-art/resource.yaml +1 -1
  246. package/library/skills/play/SKILL.md +1 -1
  247. package/library/skills/play/resource.yaml +1 -1
  248. package/library/skills/playtesting-a-feature/SKILL.md +1 -1
  249. package/library/skills/playtesting-a-feature/resource.yaml +1 -1
  250. package/library/skills/procedural-animation/SKILL.md +20 -16
  251. package/library/skills/procedural-animation/resource.yaml +1 -1
  252. package/library/skills/prop-model/SKILL.md +25 -19
  253. package/library/skills/prop-model/resource.yaml +1 -1
  254. package/library/skills/psx-retro-rendering/resource.yaml +1 -0
  255. package/library/skills/publish-your-game/SKILL.md +16 -6
  256. package/library/skills/publish-your-game/resource.yaml +3 -3
  257. package/library/skills/realtime-wet-surfaces/resource.yaml +1 -0
  258. package/library/skills/release-readiness/SKILL.md +1 -1
  259. package/library/skills/release-readiness/resource.yaml +1 -1
  260. package/library/skills/remote-deploy/SKILL.md +1 -1
  261. package/library/skills/remote-deploy/resource.yaml +1 -1
  262. package/library/skills/retarget/SKILL.md +23 -20
  263. package/library/skills/retarget/resource.yaml +1 -1
  264. package/library/skills/running-in-the-cloud/SKILL.md +1 -1
  265. package/library/skills/running-in-the-cloud/resource.yaml +1 -1
  266. package/library/skills/save-load/SKILL.md +1 -1
  267. package/library/skills/save-load/resource.yaml +1 -1
  268. package/library/skills/scene-composition/SKILL.md +1 -1
  269. package/library/skills/scene-composition/resource.yaml +1 -1
  270. package/library/skills/scene-hierarchy-design/SKILL.md +2 -2
  271. package/library/skills/scene-hierarchy-design/resource.yaml +1 -1
  272. package/library/skills/scene-scripting/SKILL.md +66 -636
  273. package/library/skills/scene-scripting/references/ctx-api.md +184 -0
  274. package/library/skills/scene-scripting/references/recipes-2d-ui.md +193 -0
  275. package/library/skills/scene-scripting/references/recipes-3d.md +280 -0
  276. package/library/skills/scene-scripting/resource.yaml +4 -4
  277. package/library/skills/scene-to-level/SKILL.md +2 -2
  278. package/library/skills/scene-to-level/resource.yaml +1 -1
  279. package/library/skills/skill-create/resource.yaml +1 -0
  280. package/library/skills/skill-improve/resource.yaml +1 -0
  281. package/library/skills/skill-test/SKILL.md +2 -2
  282. package/library/skills/skill-test/resource.yaml +1 -0
  283. package/library/skills/skintokens-auto-rigging/resource.yaml +1 -0
  284. package/library/skills/skybox-panorama/SKILL.md +27 -21
  285. package/library/skills/skybox-panorama/resource.yaml +1 -1
  286. package/library/skills/sound-effect/SKILL.md +29 -20
  287. package/library/skills/sound-effect/resource.yaml +1 -1
  288. package/library/skills/spatial-placement/SKILL.md +14 -9
  289. package/library/skills/spatial-placement/resource.yaml +1 -1
  290. package/library/skills/sprite-sheet/SKILL.md +32 -27
  291. package/library/skills/sprite-sheet/resource.yaml +1 -1
  292. package/library/skills/store-art/SKILL.md +3 -3
  293. package/library/skills/store-art/resource.yaml +2 -2
  294. package/library/skills/store-listing/SKILL.md +3 -3
  295. package/library/skills/store-listing/resource.yaml +2 -2
  296. package/library/skills/summer-analytics/SKILL.md +7 -5
  297. package/library/skills/summer-analytics/resource.yaml +2 -2
  298. package/library/skills/summer-apps/SKILL.md +48 -32
  299. package/library/skills/summer-apps/manifest.md +1 -1
  300. package/library/skills/summer-apps/minimal-app.md +7 -7
  301. package/library/skills/summer-apps/resource.yaml +1 -1
  302. package/library/skills/summer-apps/sdk-reference.md +5 -5
  303. package/library/skills/summer-apps-design/SKILL.md +1 -1
  304. package/library/skills/summer-apps-design/resource.yaml +1 -1
  305. package/library/skills/summer-assets/SKILL.md +56 -0
  306. package/library/skills/summer-assets/resource.yaml +34 -0
  307. package/library/skills/summer-store/SKILL.md +1 -1
  308. package/library/skills/text-to-motion/SKILL.md +30 -23
  309. package/library/skills/text-to-motion/prompts-and-controls.md +5 -5
  310. package/library/skills/text-to-motion/resource.yaml +1 -1
  311. package/library/skills/text-to-motion/rigging-for-text-motion.md +2 -2
  312. package/library/skills/tileable-texture/SKILL.md +19 -15
  313. package/library/skills/tileable-texture/resource.yaml +1 -1
  314. package/library/skills/trailer-shot/SKILL.md +28 -24
  315. package/library/skills/trailer-shot/resource.yaml +1 -1
  316. package/library/skills/tune-performance/SKILL.md +6 -6
  317. package/library/skills/tune-performance/resource.yaml +1 -1
  318. package/library/skills/ui-basics/SKILL.md +41 -36
  319. package/library/skills/ui-basics/resource.yaml +1 -1
  320. package/library/skills/ui-graphics/SKILL.md +47 -38
  321. package/library/skills/ui-graphics/resource.yaml +1 -1
  322. package/library/skills/use-widget-asset/SKILL.md +1 -1
  323. package/library/skills/use-widget-asset/resource.yaml +1 -1
  324. package/library/skills/using-summer/SKILL.md +6 -5
  325. package/library/skills/using-summer/resource.yaml +1 -1
  326. package/library/skills/vehicle-model/SKILL.md +65 -57
  327. package/library/skills/vehicle-model/resource.yaml +1 -1
  328. package/library/skills/verification-before-completion/SKILL.md +1 -1
  329. package/library/skills/verification-before-completion/resource.yaml +1 -1
  330. package/library/skills/verifying-scenes/SKILL.md +2 -2
  331. package/library/skills/verifying-scenes/resource.yaml +1 -1
  332. package/library/skills/vfx-dissolve/SKILL.md +2 -2
  333. package/library/skills/vfx-dissolve/resource.yaml +1 -1
  334. package/library/skills/vfx-fire/SKILL.md +15 -41
  335. package/library/skills/vfx-fire/resource.yaml +1 -1
  336. package/library/skills/vfx-hit-spark/SKILL.md +1 -1
  337. package/library/skills/vfx-hit-spark/resource.yaml +1 -1
  338. package/library/skills/vfx-lightning/SKILL.md +7 -16
  339. package/library/skills/vfx-lightning/resource.yaml +1 -1
  340. package/library/skills/vfx-magic-glow/SKILL.md +23 -32
  341. package/library/skills/vfx-magic-glow/resource.yaml +1 -1
  342. package/library/skills/vfx-muzzle-flash/SKILL.md +20 -35
  343. package/library/skills/vfx-muzzle-flash/resource.yaml +1 -1
  344. package/library/skills/vfx-smoke/SKILL.md +13 -31
  345. package/library/skills/vfx-smoke/resource.yaml +1 -1
  346. package/library/skills/vfx-water-ripple/SKILL.md +12 -31
  347. package/library/skills/vfx-water-ripple/resource.yaml +1 -1
  348. package/library/skills/voice-line/SKILL.md +71 -40
  349. package/library/skills/voice-line/resource.yaml +1 -1
  350. package/library/skills/web-and-touch-input/SKILL.md +1 -1
  351. package/library/skills/web-and-touch-input/resource.yaml +1 -1
  352. package/library/skills/web-compatibility-look/SKILL.md +1 -1
  353. package/library/skills/web-compatibility-look/resource.yaml +1 -1
  354. package/library/skills/world-building-3d/SKILL.md +1 -1
  355. package/library/skills/world-building-3d/resource.yaml +1 -1
  356. package/library/skills/writing-skills/resource.yaml +1 -0
  357. package/library/tools/add-node/GUIDE.md +14 -0
  358. package/library/tools/align-distribute-3d/GUIDE.md +7 -0
  359. package/library/tools/app-call/resource.yaml +1 -1
  360. package/library/tools/app-guide/resource.yaml +2 -2
  361. package/library/tools/app-page/resource.yaml +1 -1
  362. package/library/tools/app-screenshot/resource.yaml +19 -1
  363. package/library/tools/app-tools/resource.yaml +1 -1
  364. package/library/tools/attach-to-surface/GUIDE.md +15 -0
  365. package/library/tools/batch/GUIDE.md +27 -0
  366. package/library/tools/camera-bookmark/GUIDE.md +12 -0
  367. package/library/tools/capture-gameplay/GUIDE.md +3 -0
  368. package/library/tools/capture-gameplay/resource.yaml +3 -3
  369. package/library/tools/check-job/resource.yaml +1 -1
  370. package/library/tools/clear-console/GUIDE.md +3 -0
  371. package/library/tools/connect-ports/GUIDE.md +9 -0
  372. package/library/tools/connect-signal/GUIDE.md +14 -0
  373. package/library/tools/create-debug-report/GUIDE.md +10 -0
  374. package/library/tools/create-scene/GUIDE.md +10 -0
  375. package/library/tools/debug-views/GUIDE.md +7 -0
  376. package/library/tools/enable-plugin/GUIDE.md +3 -0
  377. package/library/tools/export-game/GUIDE.md +3 -0
  378. package/library/tools/export-game/resource.yaml +3 -3
  379. package/library/tools/export-templates/GUIDE.md +3 -0
  380. package/library/tools/fabricate-3d/GUIDE.md +19 -0
  381. package/library/tools/frame-nodes/GUIDE.md +9 -0
  382. package/library/tools/frame-shot/GUIDE.md +9 -0
  383. package/library/tools/game-control/GUIDE.md +9 -0
  384. package/library/tools/game-input/GUIDE.md +9 -0
  385. package/library/tools/game-probe/GUIDE.md +9 -0
  386. package/library/tools/generate-3d/resource.yaml +3 -3
  387. package/library/tools/generate-motion/resource.yaml +2 -2
  388. package/library/tools/get-console/GUIDE.md +9 -0
  389. package/library/tools/get-console/resource.yaml +5 -0
  390. package/library/tools/get-debugger-errors/GUIDE.md +7 -0
  391. package/library/tools/get-debugger-errors/resource.yaml +5 -0
  392. package/library/tools/get-debugger-warnings/GUIDE.md +7 -0
  393. package/library/tools/get-debugger-warnings/resource.yaml +5 -0
  394. package/library/tools/get-diagnostics/GUIDE.md +13 -0
  395. package/library/tools/get-project-context/GUIDE.md +25 -0
  396. package/library/tools/get-runtime-tree/GUIDE.md +7 -0
  397. package/library/tools/get-runtime-tree/resource.yaml +5 -0
  398. package/library/tools/get-scene-tree/GUIDE.md +11 -0
  399. package/library/tools/get-scene-tree/resource.yaml +5 -0
  400. package/library/tools/get-script-errors/GUIDE.md +5 -0
  401. package/library/tools/grep/GUIDE.md +11 -0
  402. package/library/tools/import-asset/GUIDE.md +8 -0
  403. package/library/tools/import-asset-by-id/GUIDE.md +12 -0
  404. package/library/tools/import-from-url/GUIDE.md +10 -0
  405. package/library/tools/import-from-url-batch/GUIDE.md +3 -0
  406. package/library/tools/import-hdri/GUIDE.md +16 -0
  407. package/library/tools/input-map-bind/GUIDE.md +11 -0
  408. package/library/tools/inspect-asset/GUIDE.md +14 -0
  409. package/library/tools/inspect-asset/resource.yaml +5 -0
  410. package/library/tools/inspect-node/GUIDE.md +11 -0
  411. package/library/tools/inspect-node/resource.yaml +5 -0
  412. package/library/tools/inspect-resource/GUIDE.md +7 -0
  413. package/library/tools/inspect-resource/resource.yaml +5 -0
  414. package/library/tools/inspect-runtime-node/GUIDE.md +7 -0
  415. package/library/tools/inspect-runtime-node/resource.yaml +5 -0
  416. package/library/tools/instantiate-scene/GUIDE.md +12 -0
  417. package/library/tools/is-running/GUIDE.md +3 -0
  418. package/library/tools/measure/GUIDE.md +8 -0
  419. package/library/tools/navigation-probe/GUIDE.md +9 -0
  420. package/library/tools/open/resource.yaml +2 -2
  421. package/library/tools/open-main-scene/GUIDE.md +6 -0
  422. package/library/tools/open-scene/GUIDE.md +8 -0
  423. package/library/tools/place-adjacent/GUIDE.md +7 -0
  424. package/library/tools/play/GUIDE.md +13 -0
  425. package/library/tools/project-setting/GUIDE.md +9 -0
  426. package/library/tools/publish-build/resource.yaml +6 -6
  427. package/library/tools/raycast/GUIDE.md +7 -0
  428. package/library/tools/read-file/GUIDE.md +10 -0
  429. package/library/tools/read-library/resource.yaml +5 -0
  430. package/library/tools/recent-events/GUIDE.md +7 -0
  431. package/library/tools/remove-node/GUIDE.md +3 -0
  432. package/library/tools/repeat-along/GUIDE.md +5 -0
  433. package/library/tools/replace-node/GUIDE.md +9 -0
  434. package/library/tools/replace-text/GUIDE.md +5 -0
  435. package/library/tools/run-editor-script/GUIDE.md +16 -0
  436. package/library/tools/run-script/GUIDE.md +30 -0
  437. package/library/tools/run-script/resource.yaml +6 -3
  438. package/library/tools/runtime-animate/GUIDE.md +7 -0
  439. package/library/tools/runtime-call/GUIDE.md +7 -0
  440. package/library/tools/runtime-set/GUIDE.md +9 -0
  441. package/library/tools/runtime-spawn/GUIDE.md +9 -0
  442. package/library/tools/save-scene/GUIDE.md +3 -0
  443. package/library/tools/scene-audit/GUIDE.md +29 -0
  444. package/library/tools/screenshot/GUIDE.md +17 -0
  445. package/library/tools/select-node/GUIDE.md +3 -0
  446. package/library/tools/set-prop/GUIDE.md +23 -0
  447. package/library/tools/set-resource-property/GUIDE.md +8 -0
  448. package/library/tools/shot-sheet/GUIDE.md +7 -0
  449. package/library/tools/snap-to-surface/GUIDE.md +17 -0
  450. package/library/tools/snapshot-diff/GUIDE.md +7 -0
  451. package/library/tools/starcast/GUIDE.md +12 -0
  452. package/library/tools/stop/GUIDE.md +3 -0
  453. package/library/tools/test-placement/GUIDE.md +7 -0
  454. package/library/tools/ui-actions/GUIDE.md +12 -0
  455. package/library/tools/ui-activate/GUIDE.md +11 -0
  456. package/library/tools/ui-screenshot/GUIDE.md +9 -0
  457. package/library/tools/ui-tree/GUIDE.md +10 -0
  458. package/library/tools/wait-for-event/GUIDE.md +11 -0
  459. package/library/tools/world-snapshot/GUIDE.md +9 -0
  460. package/library/tools/write-file/GUIDE.md +5 -0
  461. package/library/tools/zoom/GUIDE.md +5 -0
  462. package/package.json +6 -4
  463. package/registry/generated/counts.json +2 -2
  464. package/registry/generated/gemini-extension.json +4 -12
  465. package/registry/generated/index.json +433 -339
  466. package/registry/generated/marketplace.claude.json +2 -13
  467. package/registry/generated/plugin.claude.json +23 -22
  468. package/registry/generated/plugin.codex.json +4 -12
  469. package/registry/generated/plugin.cursor.json +4 -12
  470. package/registry/generated/plugin.factory.json +4 -12
  471. package/registry/generated/server.json +34 -0
  472. package/registry/generated/skills-index.md +119 -117
  473. package/registry/generated/skills-registry.json +153 -115
  474. package/registry/operations/fixtures/invalid-deprecated-without-replacement.json +60 -0
  475. package/registry/operations/fixtures/invalid-name.json +60 -0
  476. package/registry/operations/fixtures/invalid-publish-without-owner.json +82 -0
  477. package/registry/operations/fixtures/invalid-spend-without-estimate.json +42 -0
  478. package/registry/operations/fixtures/invalid-unknown-field.json +61 -0
  479. package/registry/operations/fixtures/valid-player-facing.json +82 -0
  480. package/registry/operations/fixtures/valid-read.json +60 -0
  481. package/registry/operations/fixtures/valid-spend.json +43 -0
  482. package/registry/operations/legacy-names.json +1206 -0
  483. package/registry/schemas/operation.schema.json +318 -0
  484. package/registry/schemas/skill.schema.json +4 -0
  485. package/CLAUDE.md +0 -7
@@ -0,0 +1,78 @@
1
+ import type { Answer } from "./contract.js";
2
+ /**
3
+ * Generated files on this machine (4.0). The generate operations, assets.get
4
+ * and jobs.wait take `out` here: a folder or a file path. This package keeps
5
+ * it (the hosted server never sees it), downloads each file the answer names
6
+ * and adds `localPath` beside its URL. 3D, video and animation answer with a
7
+ * job, so their files save when jobs.wait sees the job finish; a generation
8
+ * that waits for approval keeps its `out` through the approval.
9
+ *
10
+ * Without `out`, a session that saves by default (the assets profile and the
11
+ * CLI) puts a generation's files in the asset folder of the game project it
12
+ * runs in. assets.get is a read: it saves only with `out`. Sessions with
13
+ * engine tools never save by default: there summer_import_asset_by_id puts
14
+ * the file in the project, and a second copy would land next to it.
15
+ *
16
+ * A save never overwrites a file, writes only media and model file types,
17
+ * downloads only from public https hosts, and never fails the answer: what
18
+ * was not saved is a warning.
19
+ */
20
+ /** Operations whose answer names files. */
21
+ export declare const SAVES_FILES: ReadonlySet<string>;
22
+ /** Operations that take `out`. */
23
+ export declare const TAKES_OUT: ReadonlySet<string>;
24
+ export declare const OUT_DESCRIPTION: string;
25
+ /** The largest file a save writes. */
26
+ export declare const MAX_DOWNLOAD_BYTES: number;
27
+ /** An operation's input schema with `out`, for the operations that take it. */
28
+ export declare function withOutField(op: string, schema: unknown): unknown;
29
+ export type GameProject = {
30
+ root: string;
31
+ engine: "godot" | "unity" | "unreal" | "threejs";
32
+ assets: string;
33
+ };
34
+ /** The game project at or above `start` and its asset folder, or null. The walk stops below the home folder and at a repository root. */
35
+ export declare function findGameProject(start: string): Promise<GameProject | null>;
36
+ export type LookupHost = (host: string) => Promise<Array<{
37
+ address: string;
38
+ }>>;
39
+ /** An address in a range a download must not reach; IPv4-mapped IPv6 forms (::ffff:7f00:1) match the IPv4 ranges. Not an address: true. */
40
+ export declare function isPrivateAddress(address: string): boolean;
41
+ export interface FileSaverOptions {
42
+ /** Save a generation into the project's asset folder when the call has no `out`. */
43
+ saveByDefault: boolean;
44
+ /** Where relative paths and project detection start. Default: the working directory. */
45
+ cwd?: () => string;
46
+ fetch?: typeof fetch;
47
+ /** DNS for the public-host check. Default: the system resolver. */
48
+ lookup?: LookupHost;
49
+ /** The largest file to save. Default: MAX_DOWNLOAD_BYTES. */
50
+ maxBytes?: number;
51
+ }
52
+ /** Saves the files of generate, assets.get and jobs.wait answers; one per session (it remembers each job's `out`). */
53
+ export declare class FileSaver {
54
+ private readonly options;
55
+ private readonly jobs;
56
+ /** URL to the file a default save wrote this session: a second wait on a finished job reuses it. */
57
+ private readonly defaultSaves;
58
+ constructor(options: FileSaverOptions);
59
+ /** The input to send on, without `out`, and the `out` it carried. */
60
+ take(op: string, input: Record<string, unknown>): {
61
+ input: Record<string, unknown>;
62
+ out?: string;
63
+ };
64
+ /**
65
+ * The answer with its files saved and `localPath` added, or as it was.
66
+ * Saving never fails the answer (a paid generation keeps its asset ids):
67
+ * whatever goes wrong becomes a warning.
68
+ */
69
+ finish(op: string, answer: Answer, out?: string): Promise<Answer>;
70
+ private settle;
71
+ private save;
72
+ private cwd;
73
+ private targetOf;
74
+ /** Download one file into the target under a free name; notes what changed from the caller's file name. */
75
+ private download;
76
+ /** The file in a hidden temp file in `dir` (engines skip dot files), from a public https host, within the size limit. */
77
+ private fetchToTemp;
78
+ }
@@ -0,0 +1,419 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { lookup } from "node:dns/promises";
3
+ import { constants } from "node:fs";
4
+ import { copyFile, link, mkdir, open, readdir, readFile, stat, unlink } from "node:fs/promises";
5
+ import { BlockList, isIP } from "node:net";
6
+ import { homedir } from "node:os";
7
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
+ /**
9
+ * Generated files on this machine (4.0). The generate operations, assets.get
10
+ * and jobs.wait take `out` here: a folder or a file path. This package keeps
11
+ * it (the hosted server never sees it), downloads each file the answer names
12
+ * and adds `localPath` beside its URL. 3D, video and animation answer with a
13
+ * job, so their files save when jobs.wait sees the job finish; a generation
14
+ * that waits for approval keeps its `out` through the approval.
15
+ *
16
+ * Without `out`, a session that saves by default (the assets profile and the
17
+ * CLI) puts a generation's files in the asset folder of the game project it
18
+ * runs in. assets.get is a read: it saves only with `out`. Sessions with
19
+ * engine tools never save by default: there summer_import_asset_by_id puts
20
+ * the file in the project, and a second copy would land next to it.
21
+ *
22
+ * A save never overwrites a file, writes only media and model file types,
23
+ * downloads only from public https hosts, and never fails the answer: what
24
+ * was not saved is a warning.
25
+ */
26
+ /** Operations whose answer names files. */
27
+ export const SAVES_FILES = new Set([
28
+ "assets.generate_image",
29
+ "assets.generate_audio",
30
+ "assets.generate_3d",
31
+ "assets.generate_video",
32
+ "assets.generate_motion",
33
+ "assets.get",
34
+ ]);
35
+ const JOBS_WAIT = "jobs.wait";
36
+ /** Operations that take `out`. */
37
+ export const TAKES_OUT = new Set([...SAVES_FILES, JOBS_WAIT]);
38
+ export const OUT_DESCRIPTION = "Save the file on this machine: a folder, or a file path (res:// in a Godot project). The answer adds localPath. " +
39
+ "Without out, a generation in the assets profile or the CLI saves into the project's asset folder (Godot assets/, Unity Assets/Summer, Unreal Content/Summer, three.js public/assets).";
40
+ const DOWNLOAD_TIMEOUT_MS = 120_000;
41
+ /** The largest file a save writes. */
42
+ export const MAX_DOWNLOAD_BYTES = 500 * 1024 * 1024;
43
+ const MAX_REDIRECTS = 5;
44
+ /** The file types a save writes: media and models, never code or a library a game engine would load. */
45
+ const ALLOWED_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".mp3", ".wav", ".ogg", ".mp4", ".webm", ".glb", ".gltf", ".fbx", ".obj"]);
46
+ const EXTENSIONS = {
47
+ "image/png": ".png",
48
+ "image/jpeg": ".jpg",
49
+ "image/webp": ".webp",
50
+ "image/gif": ".gif",
51
+ "audio/mpeg": ".mp3",
52
+ "audio/mp3": ".mp3",
53
+ "audio/wav": ".wav",
54
+ "audio/x-wav": ".wav",
55
+ "audio/ogg": ".ogg",
56
+ "video/mp4": ".mp4",
57
+ "video/webm": ".webm",
58
+ "model/gltf-binary": ".glb",
59
+ "model/gltf+json": ".gltf",
60
+ };
61
+ /** An operation's input schema with `out`, for the operations that take it. */
62
+ export function withOutField(op, schema) {
63
+ if (!TAKES_OUT.has(op) || !schema || typeof schema !== "object" || Array.isArray(schema))
64
+ return schema;
65
+ const record = schema;
66
+ return { ...record, properties: { ...(record.properties ?? {}), out: { type: "string", description: OUT_DESCRIPTION } } };
67
+ }
68
+ /** The game project at or above `start` and its asset folder, or null. The walk stops below the home folder and at a repository root. */
69
+ export async function findGameProject(start) {
70
+ const home = homedir();
71
+ let current = resolve(start);
72
+ while (current !== home) {
73
+ let names;
74
+ try {
75
+ names = await readdir(current);
76
+ }
77
+ catch {
78
+ names = [];
79
+ }
80
+ const found = await projectIn(current, names);
81
+ if (found)
82
+ return found;
83
+ const parent = dirname(current);
84
+ if (parent === current || names.includes(".git"))
85
+ return null;
86
+ current = parent;
87
+ }
88
+ return null;
89
+ }
90
+ async function projectIn(dir, names) {
91
+ const has = (name) => names.includes(name);
92
+ if (has("project.godot"))
93
+ return { root: dir, engine: "godot", assets: join(dir, "assets") };
94
+ if (has("Assets") && has("ProjectSettings"))
95
+ return { root: dir, engine: "unity", assets: join(dir, "Assets", "Summer") };
96
+ if (names.some((name) => name.endsWith(".uproject")))
97
+ return { root: dir, engine: "unreal", assets: join(dir, "Content", "Summer") };
98
+ if (has("package.json") && (await usesThree(join(dir, "package.json"))))
99
+ return { root: dir, engine: "threejs", assets: join(dir, "public", "assets") };
100
+ return null;
101
+ }
102
+ async function usesThree(packageJson) {
103
+ try {
104
+ const parsed = JSON.parse(await readFile(packageJson, "utf8"));
105
+ return Boolean(parsed.dependencies?.three ?? parsed.devDependencies?.three);
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ const asRecord = (value) => (value && typeof value === "object" && !Array.isArray(value) ? value : null);
112
+ const text = (value) => (typeof value === "string" && value.trim() ? value.trim() : undefined);
113
+ const urlOf = (holder) => text(holder.downloadUrl) ?? text(holder.fileUrl);
114
+ const messageOf = (error) => (error instanceof Error ? error.message : String(error)).replace(/\.$/, "");
115
+ /** The objects in a result that carry a file URL: each of `assets`, `asset`, and the result itself. */
116
+ function holdersOf(result) {
117
+ const record = asRecord(result);
118
+ if (!record)
119
+ return [];
120
+ const list = [...(Array.isArray(record.assets) ? record.assets : []), record.asset, record];
121
+ return list.map(asRecord).filter((holder) => holder !== null && urlOf(holder) !== undefined);
122
+ }
123
+ function withWarnings(answer, warnings) {
124
+ return answer.ok && warnings.length ? { ...answer, warnings: [...(answer.warnings ?? []), ...warnings] } : answer;
125
+ }
126
+ async function isFile(path) {
127
+ try {
128
+ return (await stat(path)).isFile();
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
134
+ async function isDirectory(path) {
135
+ try {
136
+ return (await stat(path)).isDirectory();
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function slug(value) {
143
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/, "");
144
+ }
145
+ const allowed = (ext) => (ext && ALLOWED_EXTENSIONS.has(ext.toLowerCase()) ? ext : undefined);
146
+ const lookupAll = (host) => lookup(host, { all: true });
147
+ /** Unspecified, loopback, private, link-local, carrier-grade NAT, benchmark, multicast, reserved and NAT64 ranges. */
148
+ const PRIVATE_RANGES = new BlockList();
149
+ for (const [network, prefix] of [["0.0.0.0", 8], ["10.0.0.0", 8], ["100.64.0.0", 10], ["127.0.0.0", 8], ["169.254.0.0", 16], ["172.16.0.0", 12], ["192.0.0.0", 24], ["192.168.0.0", 16], ["198.18.0.0", 15], ["224.0.0.0", 3]]) {
150
+ PRIVATE_RANGES.addSubnet(network, prefix, "ipv4");
151
+ }
152
+ for (const [network, prefix] of [["::", 96], ["fc00::", 7], ["fe80::", 10], ["ff00::", 8], ["64:ff9b::", 96], ["64:ff9b:1::", 48]]) {
153
+ PRIVATE_RANGES.addSubnet(network, prefix, "ipv6");
154
+ }
155
+ /** An address in a range a download must not reach; IPv4-mapped IPv6 forms (::ffff:7f00:1) match the IPv4 ranges. Not an address: true. */
156
+ export function isPrivateAddress(address) {
157
+ const family = isIP(address);
158
+ return family === 0 || PRIVATE_RANGES.check(address, family === 4 ? "ipv4" : "ipv6");
159
+ }
160
+ /** The URL, when it is https on a public host; else why not. */
161
+ async function publicHttps(raw, lookupHost) {
162
+ let url;
163
+ try {
164
+ url = new URL(raw);
165
+ }
166
+ catch {
167
+ throw new Error("the file URL is not valid");
168
+ }
169
+ if (url.protocol !== "https:")
170
+ throw new Error(`only https downloads are saved, not ${url.protocol}`);
171
+ const host = url.hostname.replace(/^\[|\]$/g, "");
172
+ if (host === "localhost" || host.endsWith(".localhost"))
173
+ throw new Error(`${host} is a local address`);
174
+ const addresses = isIP(host) ? [host] : (await lookupHost(host)).map((entry) => entry.address);
175
+ if (addresses.length === 0 || addresses.some(isPrivateAddress))
176
+ throw new Error(`${host} is a private or local address`);
177
+ return url;
178
+ }
179
+ /** Link the downloaded file to the first free name (never over an existing file); returns the path. */
180
+ async function claimName(temp, dir, base, ext) {
181
+ for (let n = 1; n <= 10_000; n++) {
182
+ const candidate = join(dir, `${base}${n === 1 ? "" : `-${n}`}${ext}`);
183
+ try {
184
+ await link(temp, candidate);
185
+ return candidate;
186
+ }
187
+ catch (error) {
188
+ if (error.code === "EEXIST")
189
+ continue;
190
+ // A disk without hard links (exFAT, some network drives): an exclusive copy.
191
+ try {
192
+ await copyFile(temp, candidate, constants.COPYFILE_EXCL);
193
+ return candidate;
194
+ }
195
+ catch (copyError) {
196
+ if (copyError.code === "EEXIST")
197
+ continue;
198
+ throw copyError;
199
+ }
200
+ }
201
+ }
202
+ throw new Error(`no free file name for ${base}${ext} in ${dir}`);
203
+ }
204
+ /** Saves the files of generate, assets.get and jobs.wait answers; one per session (it remembers each job's `out`). */
205
+ export class FileSaver {
206
+ options;
207
+ jobs = new Map();
208
+ /** URL to the file a default save wrote this session: a second wait on a finished job reuses it. */
209
+ defaultSaves = new Map();
210
+ constructor(options) {
211
+ this.options = options;
212
+ }
213
+ /** The input to send on, without `out`, and the `out` it carried. */
214
+ take(op, input) {
215
+ if (!TAKES_OUT.has(op) || !("out" in input))
216
+ return { input };
217
+ const { out, ...rest } = input;
218
+ const chosen = text(out);
219
+ return { input: rest, ...(chosen ? { out: chosen } : {}) };
220
+ }
221
+ /**
222
+ * The answer with its files saved and `localPath` added, or as it was.
223
+ * Saving never fails the answer (a paid generation keeps its asset ids):
224
+ * whatever goes wrong becomes a warning.
225
+ */
226
+ async finish(op, answer, out) {
227
+ try {
228
+ return await this.settle(op, answer, out);
229
+ }
230
+ catch (error) {
231
+ return withWarnings(answer, [`Not saved on this machine: ${messageOf(error)}.`]);
232
+ }
233
+ }
234
+ async settle(op, answer, out) {
235
+ if (!answer.ok)
236
+ return answer;
237
+ if (op === "ops.describe" && answer.status === "done")
238
+ return describedWithOut(answer);
239
+ if (!TAKES_OUT.has(op))
240
+ return answer;
241
+ // assets.get is a read: it saves only when asked.
242
+ const byDefault = op !== "assets.get";
243
+ if (answer.status === "running" || answer.status === "needs_approval") {
244
+ if (op !== JOBS_WAIT)
245
+ this.jobs.set(answer.job.id, { out, byDefault });
246
+ return answer;
247
+ }
248
+ if (answer.status !== "done")
249
+ return answer;
250
+ if (op !== JOBS_WAIT)
251
+ return this.save(answer, (copy) => copy.result, out, byDefault);
252
+ const wait = asRecord(answer.result);
253
+ const job = asRecord(wait?.job);
254
+ const id = text(job?.id);
255
+ if (!job || !id)
256
+ return answer;
257
+ const memory = this.jobs.get(id);
258
+ if (wait?.finished !== true) {
259
+ if (out)
260
+ this.jobs.set(id, { out, byDefault: memory?.byDefault ?? true });
261
+ return answer;
262
+ }
263
+ this.jobs.delete(id);
264
+ if (job.state !== "done")
265
+ return answer;
266
+ const chosen = out ?? memory?.out;
267
+ // An approved generation that runs on as a job: its `out` follows that job.
268
+ const result = asRecord(job.result);
269
+ const next = result?.status === "running" ? text(asRecord(result.job)?.id) : undefined;
270
+ if (next) {
271
+ if (memory || chosen)
272
+ this.jobs.set(next, { out: chosen, byDefault: memory?.byDefault ?? true });
273
+ return answer;
274
+ }
275
+ // Generation jobs (and approved generate calls) save by default; another job's result only when asked.
276
+ const generated = job.kind === "gen" || id.startsWith("gen:");
277
+ return this.save(answer, (copy) => asRecord(asRecord(copy.result)?.job)?.result, chosen, memory ? memory.byDefault : generated);
278
+ }
279
+ async save(answer, resultOf, out, byDefault) {
280
+ if (holdersOf(resultOf(answer)).length === 0) {
281
+ return out ? withWarnings(answer, ["out: this answer names no file, so nothing was saved on this machine."]) : answer;
282
+ }
283
+ const target = await this.targetOf(out, byDefault && this.options.saveByDefault);
284
+ if (!target)
285
+ return answer;
286
+ if ("error" in target)
287
+ return withWarnings(answer, [target.error]);
288
+ const copy = structuredClone(answer);
289
+ const warnings = [];
290
+ // With out the caller asked for a copy there; without it, a file this session already saved is reused.
291
+ const saved = out ? new Map() : this.defaultSaves;
292
+ let first = true;
293
+ for (const holder of holdersOf(resultOf(copy))) {
294
+ const url = urlOf(holder);
295
+ const known = saved.get(url);
296
+ if (known && (await isFile(known))) {
297
+ holder.localPath = known;
298
+ continue;
299
+ }
300
+ try {
301
+ const path = await this.download(url, holder, target, first, warnings);
302
+ first = false;
303
+ saved.set(url, path);
304
+ holder.localPath = path;
305
+ }
306
+ catch (error) {
307
+ warnings.push(`Not saved on this machine (${text(holder.assetId) ?? text(holder.id) ?? "file"}): ${messageOf(error)}.`);
308
+ }
309
+ }
310
+ return withWarnings(copy, warnings);
311
+ }
312
+ cwd() {
313
+ return this.options.cwd?.() ?? process.cwd();
314
+ }
315
+ async targetOf(out, byDefault) {
316
+ if (!out) {
317
+ if (!byDefault)
318
+ return null;
319
+ const project = await findGameProject(this.cwd());
320
+ return project ? { dir: project.assets } : null;
321
+ }
322
+ let path;
323
+ if (out.startsWith("res://")) {
324
+ const cwd = this.cwd();
325
+ const project = await findGameProject(cwd);
326
+ if (project?.engine !== "godot")
327
+ return { error: `out: a res:// path needs a Godot project (project.godot) at or above ${cwd}. Nothing was saved on this machine.` };
328
+ path = join(project.root, out.slice("res://".length));
329
+ const inside = relative(project.root, path);
330
+ if (inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside))
331
+ return { error: `out: ${out} is outside the Godot project. Nothing was saved on this machine.` };
332
+ }
333
+ else if (out === "~" || out.startsWith("~/")) {
334
+ path = join(homedir(), out.slice(1));
335
+ }
336
+ else {
337
+ path = isAbsolute(out) ? out : resolve(this.cwd(), out);
338
+ }
339
+ const folder = /[\\/]$/.test(out) || !extname(path) || (await isDirectory(path));
340
+ return folder ? { dir: path } : { dir: dirname(path), file: basename(path) };
341
+ }
342
+ /** Download one file into the target under a free name; notes what changed from the caller's file name. */
343
+ async download(url, holder, target, first, warnings) {
344
+ const { temp, finalUrl, contentType } = await this.fetchToTemp(url, target.dir);
345
+ try {
346
+ const detected = allowed(extname(text(holder.fileName) ?? "")) ?? allowed(extname(finalUrl.pathname)) ?? EXTENSIONS[contentType ?? ""] ?? EXTENSIONS[text(holder.mimeType) ?? ""] ?? ".bin";
347
+ if (target.file) {
348
+ const asked = extname(target.file);
349
+ const ext = allowed(asked) ?? detected;
350
+ const path = await claimName(temp, target.dir, basename(target.file, asked), ext);
351
+ const wanted = join(target.dir, target.file);
352
+ if (first && path !== wanted) {
353
+ warnings.push(allowed(asked) ? `${wanted} already exists, so the file was saved as ${path}.` : `out: ${asked || "no extension"} is not a media or model file type, so the file was saved as ${path}.`);
354
+ }
355
+ return path;
356
+ }
357
+ const named = text(holder.fileName);
358
+ const base = (named && slug(basename(named, extname(named)))) || slug(text(holder.title) ?? "") || slug(text(holder.assetId) ?? text(holder.id) ?? "") || "summer-asset";
359
+ return await claimName(temp, target.dir, base, detected);
360
+ }
361
+ finally {
362
+ await unlink(temp).catch(() => undefined);
363
+ }
364
+ }
365
+ /** The file in a hidden temp file in `dir` (engines skip dot files), from a public https host, within the size limit. */
366
+ async fetchToTemp(url, dir) {
367
+ const lookupHost = this.options.lookup ?? lookupAll;
368
+ const fetchFile = this.options.fetch ?? fetch;
369
+ let current = await publicHttps(url, lookupHost);
370
+ let response;
371
+ for (let hops = 0;; hops++) {
372
+ response = await fetchFile(current.href, { redirect: "manual", signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
373
+ const location = response.headers.get("location");
374
+ if (response.status < 300 || response.status >= 400 || !location)
375
+ break;
376
+ await response.body?.cancel().catch(() => undefined);
377
+ if (hops >= MAX_REDIRECTS)
378
+ throw new Error("the download redirected too many times");
379
+ current = await publicHttps(new URL(location, current).href, lookupHost);
380
+ }
381
+ if (!response.ok)
382
+ throw new Error(`the download answered HTTP ${response.status}`);
383
+ if (response.url)
384
+ current = await publicHttps(response.url, lookupHost);
385
+ const maxBytes = this.options.maxBytes ?? MAX_DOWNLOAD_BYTES;
386
+ const limit = maxBytes >= 1024 * 1024 ? `${Math.floor(maxBytes / 1024 / 1024)} MB` : `${maxBytes} bytes`;
387
+ if (Number(response.headers.get("content-length") ?? 0) > maxBytes)
388
+ throw new Error(`the file is larger than ${limit}`);
389
+ await mkdir(dir, { recursive: true });
390
+ const temp = join(dir, `.summer-download-${randomBytes(6).toString("hex")}.part`);
391
+ const handle = await open(temp, "wx");
392
+ try {
393
+ let size = 0;
394
+ if (response.body) {
395
+ for await (const chunk of response.body) {
396
+ size += chunk.byteLength;
397
+ if (size > maxBytes)
398
+ throw new Error(`the file is larger than ${limit}`);
399
+ await handle.write(chunk);
400
+ }
401
+ }
402
+ await handle.close();
403
+ }
404
+ catch (error) {
405
+ await handle.close().catch(() => undefined);
406
+ await unlink(temp).catch(() => undefined);
407
+ throw error;
408
+ }
409
+ return { temp, finalUrl: current, contentType: response.headers.get("content-type")?.split(";")[0].trim() };
410
+ }
411
+ }
412
+ /** ops.describe of an operation that takes `out`: its input shows `out` too. */
413
+ function describedWithOut(answer) {
414
+ const operation = asRecord(asRecord(answer.result)?.operation);
415
+ const name = text(operation?.name);
416
+ if (!operation || !name || !TAKES_OUT.has(name))
417
+ return answer;
418
+ return { ...answer, result: { ...asRecord(answer.result), operation: { ...operation, input: withOutField(name, operation.input) } } };
419
+ }
@@ -0,0 +1,9 @@
1
+ import type { CatalogEntry } from "./catalog.js";
2
+ import type { Effect } from "./contract.js";
3
+ export declare function words(text: string): string[];
4
+ export declare function scoreEntry(entry: CatalogEntry, query: string): number;
5
+ export declare function searchEntries(entries: readonly CatalogEntry[], query: string, filters: {
6
+ area?: string;
7
+ effect?: Effect;
8
+ limit: number;
9
+ }): CatalogEntry[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Plain-words ranking over operations, the same scoring the hosted server
3
+ * uses, so one list can hold local and cloud hits.
4
+ */
5
+ const STOP = new Set(["a", "an", "the", "my", "of", "for", "to", "in", "on", "and", "or", "with", "is", "it", "me", "i", "please", "how", "do", "what"]);
6
+ export function words(text) {
7
+ return text
8
+ .toLowerCase()
9
+ .split(/[^a-z0-9]+/)
10
+ .filter((word) => word.length > 1 && !STOP.has(word));
11
+ }
12
+ const stem = (word) => word.length > 3 && word.endsWith("ies") ? `${word.slice(0, -3)}y` : word.length > 3 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word;
13
+ export function scoreEntry(entry, query) {
14
+ const terms = words(query);
15
+ if (!terms.length)
16
+ return 0;
17
+ const exact = query.trim().toLowerCase();
18
+ if (exact === entry.name || entry.legacy?.some((name) => name.split("#")[0] === exact))
19
+ return 1000;
20
+ const fields = [
21
+ [words(entry.name.replace(/[._]/g, " ")), 6],
22
+ [words(entry.title), 4],
23
+ [(entry.tags ?? []).flatMap(words), 3],
24
+ [(entry.legacy ?? []).flatMap((name) => words(name.replace(/^summer_/, "").replace(/[#_]/g, " "))), 2],
25
+ [words(entry.summary), 1],
26
+ ];
27
+ let total = 0;
28
+ for (const term of terms.map(stem)) {
29
+ let best = 0;
30
+ for (const [fieldWords, weight] of fields) {
31
+ for (const word of fieldWords) {
32
+ const candidate = stem(word);
33
+ if (candidate === term)
34
+ best = Math.max(best, weight);
35
+ else if (term.length >= 4 && candidate.length >= 3 && (candidate.startsWith(term) || term.startsWith(candidate)))
36
+ best = Math.max(best, weight / 2);
37
+ }
38
+ }
39
+ total += best;
40
+ }
41
+ return total;
42
+ }
43
+ const STATUS_RANK = { stable: 0, preview: 1, deprecated: 2 };
44
+ export function searchEntries(entries, query, filters) {
45
+ const hasTerms = words(query).length > 0;
46
+ return entries
47
+ .filter((entry) => entry.status !== "deprecated")
48
+ .filter((entry) => !filters.area || entry.name === filters.area || entry.name.startsWith(`${filters.area}.`))
49
+ .filter((entry) => !filters.effect || entry.effect === filters.effect)
50
+ .map((entry) => ({ entry, score: scoreEntry(entry, query) }))
51
+ .filter(({ score }) => score > 0 || !hasTerms)
52
+ .sort((a, b) => b.score - a.score || STATUS_RANK[a.entry.status] - STATUS_RANK[b.entry.status] || Number(b.entry.available) - Number(a.entry.available) || a.entry.name.localeCompare(b.entry.name))
53
+ .slice(0, filters.limit)
54
+ .map(({ entry }) => entry);
55
+ }
@@ -25,6 +25,10 @@ export interface SkillRegistryEntry {
25
25
  path: string;
26
26
  /** facets.domains from resource.yaml; the first is the primary domain. Empty on old registries. */
27
27
  domains: string[];
28
+ /** Bulk installs and the plugin manifests take it. false = library only
29
+ * (library-author tools, research digests): readable with
30
+ * summer_read_library and installable by name. Absent reads as true. */
31
+ bundled?: boolean;
28
32
  }
29
33
  /**
30
34
  * Load the generated skill registry (cached after first read).
@@ -34,18 +38,29 @@ export interface SkillRegistryEntry {
34
38
  export declare function getSkillRegistry(): readonly SkillRegistryEntry[];
35
39
  /** Shape-tolerant parse of skills-registry.json. Exported for unit tests. */
36
40
  export declare function parseSkillRegistry(json: unknown): SkillRegistryEntry[];
41
+ /**
42
+ * The assets-only install (`summer setup --assets-only`, `skills install
43
+ * --assets`): skills that make sounds, music, voices, images, models,
44
+ * animation and video on Summer's servers, plus `summer-assets`, the session
45
+ * skill for that profile. Each says what to do without Summer Engine. The
46
+ * engine session skill `using-summer` and skills that wire assets into a
47
+ * running engine stay out.
48
+ */
49
+ export declare function isAssetSkill(skill: Pick<SkillRegistryEntry, "name" | "domains">): boolean;
37
50
  /**
38
51
  * The one bulk-install rule (`skills install --all/--recommended` and
39
52
  * `summer setup` both use it): `stable` and `preview` skills install —
40
53
  * preview is a label ("not yet exercised in-engine by the Summer team"),
41
54
  * carried in each skill's own guidance, not a reason to hide contributed work;
42
- * `deprecated` never installs in bulk. `stableOnly` opts out of preview.
55
+ * `deprecated` never installs in bulk, and neither does a library-only skill
56
+ * (`bundled: false`). `stableOnly` opts out of preview.
43
57
  * The counts let the caller say how many preview skills it installed (or, with
44
58
  * stableOnly, left out) instead of printing a bare total.
45
59
  */
46
60
  export declare function selectSkillsForBulkInstall(skills: readonly SkillRegistryEntry[], options: {
47
61
  recommended?: boolean;
48
62
  stableOnly?: boolean;
63
+ assetsOnly?: boolean;
49
64
  }): {
50
65
  selected: SkillRegistryEntry[];
51
66
  previewIncluded: number;
@@ -78,19 +78,52 @@ export function parseSkillRegistry(json) {
78
78
  status: s.status === "preview" || s.status === "deprecated" ? s.status : "stable",
79
79
  path: s.path,
80
80
  domains: Array.isArray(s.domains) ? s.domains.filter((d) => typeof d === "string") : [],
81
+ bundled: s.bundled !== false,
81
82
  }));
82
83
  }
84
+ /** Skills that handle assets but need more than the assets profile: they wire
85
+ * assets into a scene, or (store-art) make art for a Summer Games listing. */
86
+ const NOT_ASSETS_ONLY_SKILLS = new Set([
87
+ "instantiate-asset-pack",
88
+ "use-widget-asset",
89
+ "skintokens-auto-rigging",
90
+ "fabricating-assets",
91
+ "lumera-single-image-scene-reconstruction",
92
+ "store-art",
93
+ ]);
94
+ /**
95
+ * The assets-only install (`summer setup --assets-only`, `skills install
96
+ * --assets`): skills that make sounds, music, voices, images, models,
97
+ * animation and video on Summer's servers, plus `summer-assets`, the session
98
+ * skill for that profile. Each says what to do without Summer Engine. The
99
+ * engine session skill `using-summer` and skills that wire assets into a
100
+ * running engine stay out.
101
+ */
102
+ export function isAssetSkill(skill) {
103
+ if (skill.name === "generate-motion")
104
+ return true;
105
+ if (NOT_ASSETS_ONLY_SKILLS.has(skill.name))
106
+ return false;
107
+ const domains = skill.domains ?? [];
108
+ return domains.includes("assets") || domains.includes("generate");
109
+ }
83
110
  /**
84
111
  * The one bulk-install rule (`skills install --all/--recommended` and
85
112
  * `summer setup` both use it): `stable` and `preview` skills install —
86
113
  * preview is a label ("not yet exercised in-engine by the Summer team"),
87
114
  * carried in each skill's own guidance, not a reason to hide contributed work;
88
- * `deprecated` never installs in bulk. `stableOnly` opts out of preview.
115
+ * `deprecated` never installs in bulk, and neither does a library-only skill
116
+ * (`bundled: false`). `stableOnly` opts out of preview.
89
117
  * The counts let the caller say how many preview skills it installed (or, with
90
118
  * stableOnly, left out) instead of printing a bare total.
91
119
  */
92
120
  export function selectSkillsForBulkInstall(skills, options) {
93
- const candidates = options.recommended ? skills.filter((skill) => skill.recommended) : [...skills];
121
+ const bundled = skills.filter((skill) => skill.bundled !== false);
122
+ const candidates = options.assetsOnly
123
+ ? bundled.filter(isAssetSkill)
124
+ : options.recommended
125
+ ? bundled.filter((skill) => skill.recommended)
126
+ : bundled;
94
127
  const stableOnly = options.stableOnly === true;
95
128
  const selected = candidates.filter((skill) => skill.status === "stable" || (!stableOnly && skill.status === "preview"));
96
129
  const preview = candidates.filter((skill) => skill.status === "preview").length;