summer-engine 3.8.0 → 4.2.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.
- package/.claude-plugin/marketplace.json +2 -13
- package/.claude-plugin/plugin.json +24 -22
- package/.codex-plugin/plugin.json +18 -22
- package/.cursor-plugin/plugin.json +8 -14
- package/.factory-plugin/plugin.json +6 -13
- package/AGENTS.md +19 -12
- package/CHANGELOG.md +133 -0
- package/GEMINI.md +3 -3
- package/README.md +118 -37
- package/assets/icon.png +0 -0
- package/commands/start.md +71 -0
- package/commands/summer.md +3 -2
- package/dist/cli/banner.js +3 -0
- package/dist/cli/commands/config.js +3 -2
- package/dist/cli/commands/create.js +12 -3
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/list.js +30 -13
- package/dist/cli/commands/login.d.ts +27 -0
- package/dist/cli/commands/login.js +58 -12
- package/dist/cli/commands/memory.js +6 -2
- package/dist/cli/commands/ops.d.ts +64 -0
- package/dist/cli/commands/ops.js +329 -0
- package/dist/cli/commands/setup.d.ts +4 -0
- package/dist/cli/commands/setup.js +254 -26
- package/dist/cli/commands/skills.js +28 -11
- package/dist/cli/commands/status.d.ts +26 -0
- package/dist/cli/commands/status.js +36 -21
- package/dist/cli/index.js +7 -0
- package/dist/core/agent-host.d.ts +65 -0
- package/dist/core/agent-host.js +193 -0
- package/dist/core/capabilities/agent-playbook.d.ts +3 -0
- package/dist/core/capabilities/agent-playbook.js +54 -2
- package/dist/core/capabilities/apps.d.ts +33 -10
- package/dist/core/capabilities/apps.js +25 -12
- package/dist/core/capabilities/audit/args.d.ts +6 -6
- package/dist/core/capabilities/doctor.d.ts +3 -0
- package/dist/core/capabilities/doctor.js +20 -15
- package/dist/core/capabilities/fabricate-mesh.d.ts +4 -4
- package/dist/core/capabilities/game-task-plan.d.ts +12 -6
- package/dist/core/capabilities/game-task-plan.js +80 -5
- package/dist/core/capabilities/hdri-import.d.ts +2 -2
- package/dist/core/capabilities/motion-generation.d.ts +2 -2
- package/dist/core/capabilities/motion-generation.js +2 -2
- package/dist/core/capabilities/navigation/open.d.ts +6 -2
- package/dist/core/capabilities/navigation/open.js +3 -0
- package/dist/core/capabilities/placement.d.ts +23 -23
- package/dist/core/capabilities/publish-build.js +2 -0
- package/dist/core/capabilities/runtime-control.d.ts +42 -42
- package/dist/core/capabilities/scene-script-target.d.ts +17 -0
- package/dist/core/capabilities/scene-script-target.js +70 -0
- package/dist/core/capabilities/seeing/args.d.ts +16 -16
- package/dist/core/capabilities/sfx-choices.d.ts +4 -0
- package/dist/core/capabilities/sfx-choices.js +13 -6
- package/dist/core/capabilities/tool-dispatch.d.ts +12 -3
- package/dist/core/capabilities/tool-dispatch.js +81 -46
- package/dist/core/capabilities/ui-control.d.ts +2 -2
- package/dist/core/capabilities/world-snapshot.d.ts +4 -4
- package/dist/core/embeddings.d.ts +2 -0
- package/dist/core/embeddings.js +1 -1
- package/dist/core/error-codes.d.ts +62 -0
- package/dist/core/error-codes.js +97 -0
- package/dist/core/format.d.ts +2 -0
- package/dist/core/format.js +2 -1
- package/dist/core/hosted-mcp.d.ts +22 -0
- package/dist/core/hosted-mcp.js +56 -1
- package/dist/core/library-read.js +5 -0
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/library-search.js +2 -1
- package/dist/core/oauth.js +4 -0
- package/dist/core/ops/areas.d.ts +11 -0
- package/dist/core/ops/areas.js +24 -0
- package/dist/core/ops/catalog.d.ts +45 -0
- package/dist/core/ops/catalog.js +157 -0
- package/dist/core/ops/cloud.d.ts +64 -0
- package/dist/core/ops/cloud.js +198 -0
- package/dist/core/ops/contract.d.ts +120 -0
- package/dist/core/ops/contract.js +134 -0
- package/dist/core/ops/engine-answer.d.ts +25 -0
- package/dist/core/ops/engine-answer.js +117 -0
- package/dist/core/ops/engine.d.ts +55 -0
- package/dist/core/ops/engine.js +649 -0
- package/dist/core/ops/public-catalog.d.ts +48 -0
- package/dist/core/ops/public-catalog.js +56 -0
- package/dist/core/ops/save-files.d.ts +78 -0
- package/dist/core/ops/save-files.js +419 -0
- package/dist/core/ops/search.d.ts +8 -0
- package/dist/core/ops/search.js +187 -0
- package/dist/core/skills-registry.d.ts +16 -1
- package/dist/core/skills-registry.js +35 -2
- package/dist/core/telemetry.d.ts +32 -0
- package/dist/core/telemetry.js +38 -10
- package/dist/core/tool-profiles.d.ts +25 -0
- package/dist/core/tool-profiles.js +41 -0
- package/dist/installer/agent-config.d.ts +17 -4
- package/dist/installer/agent-config.js +27 -6
- package/dist/installer/agent-table.d.ts +12 -0
- package/dist/installer/agent-table.js +33 -9
- package/dist/installer/cli-permissions.d.ts +65 -0
- package/dist/installer/cli-permissions.js +182 -0
- package/dist/installer/setup.d.ts +4 -0
- package/dist/installer/setup.js +12 -4
- package/dist/installer/skills-refresh.d.ts +29 -0
- package/dist/installer/skills-refresh.js +261 -0
- package/dist/installer/version-check.d.ts +51 -11
- package/dist/installer/version-check.js +147 -37
- package/dist/mcp/command.js +7 -0
- package/dist/mcp/hosted-mount.d.ts +27 -2
- package/dist/mcp/hosted-mount.js +53 -23
- package/dist/mcp/legacy-toolset.d.ts +27 -0
- package/dist/mcp/legacy-toolset.js +43 -0
- package/dist/mcp/ops-surface.d.ts +59 -0
- package/dist/mcp/ops-surface.js +453 -0
- package/dist/mcp/server.d.ts +45 -11
- package/dist/mcp/server.js +263 -89
- package/dist/mcp/tool-results.d.ts +26 -0
- package/dist/mcp/tool-results.js +81 -0
- package/dist/mcp/tool-surface.d.ts +65 -0
- package/dist/mcp/tool-surface.js +317 -0
- package/dist/mcp/tools/app-tools.js +9 -2
- package/dist/mcp/tools/asset-tools.js +55 -53
- package/dist/mcp/tools/audit-tools.js +21 -21
- package/dist/mcp/tools/debug-tools.js +6 -6
- package/dist/mcp/tools/fabricate-tools.js +6 -6
- package/dist/mcp/tools/generate-tools.js +76 -70
- package/dist/mcp/tools/project-tools.d.ts +9 -4
- package/dist/mcp/tools/project-tools.js +16 -10
- package/dist/mcp/tools/scene-tools.js +12 -23
- package/dist/mcp/tools/script-tools.js +9 -5
- package/dist/mcp/tools/with-engine.d.ts +6 -0
- package/dist/mcp/tools/with-engine.js +19 -0
- package/dist/project-memory/agent-instructions.d.ts +47 -0
- package/dist/project-memory/agent-instructions.js +226 -0
- package/gemini-extension.json +6 -13
- package/hooks/pre-commit-doctor.sh +5 -4
- package/hooks/session-start.sh +24 -28
- package/integrations/README.md +1 -1
- package/integrations/claude/README.md +6 -0
- package/integrations/claude/manifest-target.json +4 -0
- package/integrations/claude-desktop/README.md +1 -1
- package/integrations/copilot-jetbrains/README.md +1 -1
- package/integrations/gemini/README.md +11 -0
- package/integrations/junie/README.md +1 -1
- package/integrations/lm-studio/README.md +1 -1
- package/integrations/trae/README.md +1 -1
- package/integrations/visual-studio/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +79 -51
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/references/product-map/resource.yaml +2 -2
- package/library/skills/3d-lighting/SKILL.md +69 -28
- package/library/skills/3d-lighting/resource.yaml +1 -1
- package/library/skills/README.md +121 -118
- package/library/skills/adaptive-music/SKILL.md +40 -23
- package/library/skills/adaptive-music/resource.yaml +1 -1
- package/library/skills/agent-playtesting/SKILL.md +23 -1
- package/library/skills/agent-playtesting/resource.yaml +1 -1
- package/library/skills/ai-characters-in-games/SKILL.md +16 -1
- package/library/skills/ai-characters-in-games/resource.yaml +1 -1
- package/library/skills/ambient-bed/SKILL.md +38 -13
- package/library/skills/ambient-bed/resource.yaml +1 -1
- package/library/skills/animated-loop/SKILL.md +60 -32
- package/library/skills/animated-loop/resource.yaml +1 -1
- package/library/skills/animation-tree/SKILL.md +27 -1
- package/library/skills/animation-tree/resource.yaml +1 -1
- package/library/skills/art-direction/SKILL.md +17 -3
- package/library/skills/art-direction/resource.yaml +1 -1
- package/library/skills/asset-strategy/SKILL.md +51 -4
- package/library/skills/asset-strategy/resource.yaml +2 -1
- package/library/skills/audio-direction/SKILL.md +26 -10
- package/library/skills/audio-direction/resource.yaml +1 -1
- package/library/skills/auto-fire-targeting/SKILL.md +15 -1
- package/library/skills/auto-fire-targeting/resource.yaml +1 -1
- package/library/skills/bounded-engine-runs/SKILL.md +1 -1
- package/library/skills/bounded-engine-runs/resource.yaml +1 -1
- package/library/skills/brainstorm-game/SKILL.md +14 -1
- package/library/skills/brainstorm-game/resource.yaml +1 -1
- package/library/skills/brainstorming/SKILL.md +1 -1
- package/library/skills/brainstorming/resource.yaml +1 -1
- package/library/skills/browse-templates/SKILL.md +11 -1
- package/library/skills/browse-templates/resource.yaml +1 -1
- package/library/skills/camera-rigs/SKILL.md +22 -2
- package/library/skills/camera-rigs/resource.yaml +1 -1
- package/library/skills/celeste-momentum-platforming/resource.yaml +1 -0
- package/library/skills/character-animation-wiring/SKILL.md +28 -4
- package/library/skills/character-animation-wiring/resource.yaml +1 -1
- package/library/skills/character-model/SKILL.md +83 -47
- package/library/skills/character-model/resource.yaml +1 -1
- package/library/skills/character-movement/SKILL.md +27 -3
- package/library/skills/character-movement/resource.yaml +1 -1
- package/library/skills/character-portrait/SKILL.md +39 -19
- package/library/skills/character-portrait/resource.yaml +1 -1
- package/library/skills/cinematic-cutscene/SKILL.md +79 -46
- package/library/skills/cinematic-cutscene/resource.yaml +1 -1
- package/library/skills/combat-basics/SKILL.md +20 -1
- package/library/skills/combat-basics/resource.yaml +1 -1
- package/library/skills/compatibility-renderer-traps/SKILL.md +12 -1
- package/library/skills/compatibility-renderer-traps/resource.yaml +1 -1
- package/library/skills/concept-art/SKILL.md +29 -12
- package/library/skills/concept-art/resource.yaml +1 -1
- package/library/skills/concept-to-level/SKILL.md +258 -0
- package/library/skills/concept-to-level/references/technical-rules.md +150 -0
- package/library/skills/concept-to-level/resource.yaml +44 -0
- package/library/skills/create-asset-sheet/SKILL.md +37 -13
- package/library/skills/create-asset-sheet/resource.yaml +1 -1
- package/library/skills/debug/SKILL.md +34 -2
- package/library/skills/debug/resource.yaml +1 -1
- package/library/skills/debugging-game-feel/SKILL.md +13 -1
- package/library/skills/debugging-game-feel/resource.yaml +1 -1
- package/library/skills/design-level/SKILL.md +40 -35
- package/library/skills/design-level/resource.yaml +1 -1
- package/library/skills/design-mechanic/SKILL.md +18 -1
- package/library/skills/design-mechanic/resource.yaml +1 -1
- package/library/skills/design-npc/SKILL.md +43 -24
- package/library/skills/design-npc/resource.yaml +1 -1
- package/library/skills/diagnosing-perf-regressions/SKILL.md +13 -1
- package/library/skills/diagnosing-perf-regressions/resource.yaml +1 -1
- package/library/skills/driving-the-editor-ui/SKILL.md +24 -1
- package/library/skills/driving-the-editor-ui/resource.yaml +1 -1
- package/library/skills/environment-kit/SKILL.md +43 -16
- package/library/skills/environment-kit/resource.yaml +1 -1
- package/library/skills/export-and-ship/SKILL.md +29 -6
- package/library/skills/export-and-ship/resource.yaml +1 -1
- package/library/skills/fabricating-assets/SKILL.md +30 -7
- package/library/skills/fabricating-assets/resource.yaml +3 -3
- package/library/skills/facial-and-lipsync/SKILL.md +36 -15
- package/library/skills/facial-and-lipsync/resource.yaml +1 -1
- package/library/skills/fps-controller/SKILL.md +50 -41
- package/library/skills/fps-controller/resource.yaml +1 -1
- package/library/skills/game-feel/SKILL.md +34 -8
- package/library/skills/game-feel/resource.yaml +1 -1
- package/library/skills/gameskill/SKILL.md +15 -4
- package/library/skills/gameskill/resource.yaml +1 -1
- package/library/skills/gdscript-patterns/SKILL.md +16 -1
- package/library/skills/gdscript-patterns/resource.yaml +1 -1
- package/library/skills/generate-motion/SKILL.md +51 -21
- package/library/skills/generate-motion/resource.yaml +1 -1
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +18 -1
- package/library/skills/godogen-visual-proof-game-generation/resource.yaml +1 -0
- package/library/skills/grow-analytics/SKILL.md +13 -3
- package/library/skills/grow-analytics/resource.yaml +1 -1
- package/library/skills/headless-scripting/SKILL.md +14 -1
- package/library/skills/headless-scripting/resource.yaml +1 -1
- package/library/skills/in-game-purchases/SKILL.md +12 -1
- package/library/skills/in-game-purchases/resource.yaml +1 -1
- package/library/skills/instantiate-asset-pack/SKILL.md +19 -2
- package/library/skills/instantiate-asset-pack/resource.yaml +1 -1
- package/library/skills/lumera-single-image-scene-reconstruction/resource.yaml +1 -0
- package/library/skills/make-game/SKILL.md +33 -4
- package/library/skills/make-game/references/2d-platformer.md +1 -1
- package/library/skills/make-game/references/3d-fps.md +1 -1
- package/library/skills/make-game/references/3d-horror.md +1 -1
- package/library/skills/make-game/references/3d-third-person.md +2 -2
- package/library/skills/make-game/resource.yaml +1 -1
- package/library/skills/multiplayer/SKILL.md +13 -1
- package/library/skills/multiplayer/resource.yaml +1 -1
- package/library/skills/multiplayer-movement/SKILL.md +19 -1
- package/library/skills/multiplayer-movement/resource.yaml +1 -1
- package/library/skills/multiplayer-project/SKILL.md +18 -1
- package/library/skills/multiplayer-project/resource.yaml +1 -1
- package/library/skills/multiplayer-publish/SKILL.md +25 -7
- package/library/skills/multiplayer-publish/resource.yaml +3 -3
- package/library/skills/multiplayer-state/SKILL.md +17 -1
- package/library/skills/multiplayer-state/resource.yaml +1 -1
- package/library/skills/multiplayer-testing/SKILL.md +16 -1
- package/library/skills/multiplayer-testing/resource.yaml +1 -1
- package/library/skills/music-track/SKILL.md +38 -13
- package/library/skills/music-track/resource.yaml +1 -1
- package/library/skills/navigate-summer/SKILL.md +26 -9
- package/library/skills/navigate-summer/resource.yaml +1 -1
- package/library/skills/new-project/SKILL.md +12 -1
- package/library/skills/new-project/resource.yaml +1 -1
- package/library/skills/organic-model/SKILL.md +48 -18
- package/library/skills/organic-model/resource.yaml +1 -1
- package/library/skills/pixel-art/SKILL.md +42 -23
- package/library/skills/pixel-art/resource.yaml +1 -1
- package/library/skills/play/SKILL.md +19 -1
- package/library/skills/play/resource.yaml +1 -1
- package/library/skills/playtesting-a-feature/SKILL.md +24 -1
- package/library/skills/playtesting-a-feature/resource.yaml +1 -1
- package/library/skills/procedural-animation/SKILL.md +38 -16
- package/library/skills/procedural-animation/resource.yaml +1 -1
- package/library/skills/prop-model/SKILL.md +48 -19
- package/library/skills/prop-model/resource.yaml +1 -1
- package/library/skills/psx-retro-rendering/resource.yaml +1 -0
- package/library/skills/publish-your-game/SKILL.md +43 -6
- package/library/skills/publish-your-game/resource.yaml +3 -3
- package/library/skills/realtime-wet-surfaces/resource.yaml +1 -0
- package/library/skills/release-readiness/SKILL.md +13 -1
- package/library/skills/release-readiness/resource.yaml +1 -1
- package/library/skills/remote-deploy/SKILL.md +15 -1
- package/library/skills/remote-deploy/resource.yaml +1 -1
- package/library/skills/retarget/SKILL.md +43 -20
- package/library/skills/retarget/resource.yaml +1 -1
- package/library/skills/running-in-the-cloud/SKILL.md +11 -1
- package/library/skills/running-in-the-cloud/resource.yaml +1 -1
- package/library/skills/save-load/SKILL.md +16 -1
- package/library/skills/save-load/resource.yaml +1 -1
- package/library/skills/scene-composition/SKILL.md +18 -1
- package/library/skills/scene-composition/resource.yaml +1 -1
- package/library/skills/scene-hierarchy-design/SKILL.md +26 -2
- package/library/skills/scene-hierarchy-design/resource.yaml +1 -1
- package/library/skills/scene-scripting/SKILL.md +96 -636
- package/library/skills/scene-scripting/references/ctx-api.md +184 -0
- package/library/skills/scene-scripting/references/recipes-2d-ui.md +193 -0
- package/library/skills/scene-scripting/references/recipes-3d.md +280 -0
- package/library/skills/scene-scripting/resource.yaml +4 -4
- package/library/skills/scene-to-level/SKILL.md +24 -2
- package/library/skills/scene-to-level/resource.yaml +1 -1
- package/library/skills/skill-create/resource.yaml +1 -0
- package/library/skills/skill-improve/resource.yaml +1 -0
- package/library/skills/skill-test/SKILL.md +12 -2
- package/library/skills/skill-test/resource.yaml +1 -0
- package/library/skills/skintokens-auto-rigging/resource.yaml +1 -0
- package/library/skills/skybox-panorama/SKILL.md +47 -21
- package/library/skills/skybox-panorama/resource.yaml +1 -1
- package/library/skills/sound-effect/SKILL.md +52 -20
- package/library/skills/sound-effect/resource.yaml +1 -1
- package/library/skills/spatial-placement/SKILL.md +44 -9
- package/library/skills/spatial-placement/resource.yaml +1 -1
- package/library/skills/sprite-sheet/SKILL.md +55 -27
- package/library/skills/sprite-sheet/resource.yaml +1 -1
- package/library/skills/store-art/SKILL.md +14 -3
- package/library/skills/store-art/resource.yaml +2 -2
- package/library/skills/store-listing/SKILL.md +13 -3
- package/library/skills/store-listing/resource.yaml +2 -2
- package/library/skills/summer-analytics/SKILL.md +22 -5
- package/library/skills/summer-analytics/resource.yaml +2 -2
- package/library/skills/summer-apps/SKILL.md +77 -32
- package/library/skills/summer-apps/manifest.md +1 -1
- package/library/skills/summer-apps/minimal-app.md +7 -7
- package/library/skills/summer-apps/resource.yaml +1 -1
- package/library/skills/summer-apps/sdk-reference.md +5 -5
- package/library/skills/summer-apps-design/SKILL.md +1 -1
- package/library/skills/summer-apps-design/resource.yaml +1 -1
- package/library/skills/summer-assets/SKILL.md +86 -0
- package/library/skills/summer-assets/resource.yaml +34 -0
- package/library/skills/summer-cli/SKILL.md +133 -0
- package/library/skills/summer-cli/resource.yaml +31 -0
- package/library/skills/summer-friends/SKILL.md +14 -0
- package/library/skills/summer-leaderboards/SKILL.md +16 -0
- package/library/skills/summer-match-results/SKILL.md +17 -0
- package/library/skills/summer-matchmaking/SKILL.md +17 -0
- package/library/skills/summer-parties/SKILL.md +14 -0
- package/library/skills/summer-player-data/SKILL.md +14 -0
- package/library/skills/summer-store/SKILL.md +16 -1
- package/library/skills/summer-world-chat/SKILL.md +14 -0
- package/library/skills/summer-world-saves/SKILL.md +14 -0
- package/library/skills/text-to-motion/SKILL.md +45 -23
- package/library/skills/text-to-motion/prompts-and-controls.md +5 -5
- package/library/skills/text-to-motion/resource.yaml +1 -1
- package/library/skills/text-to-motion/rigging-for-text-motion.md +2 -2
- package/library/skills/tileable-texture/SKILL.md +37 -15
- package/library/skills/tileable-texture/resource.yaml +1 -1
- package/library/skills/trailer-shot/SKILL.md +46 -24
- package/library/skills/trailer-shot/resource.yaml +1 -1
- package/library/skills/tune-performance/SKILL.md +32 -6
- package/library/skills/tune-performance/resource.yaml +1 -1
- package/library/skills/ui-basics/SKILL.md +55 -36
- package/library/skills/ui-basics/resource.yaml +1 -1
- package/library/skills/ui-graphics/SKILL.md +64 -38
- package/library/skills/ui-graphics/resource.yaml +1 -1
- package/library/skills/use-widget-asset/SKILL.md +15 -1
- package/library/skills/use-widget-asset/resource.yaml +1 -1
- package/library/skills/using-summer/SKILL.md +32 -5
- package/library/skills/using-summer/resource.yaml +3 -2
- package/library/skills/vehicle-model/SKILL.md +92 -57
- package/library/skills/vehicle-model/resource.yaml +1 -1
- package/library/skills/verification-before-completion/SKILL.md +1 -1
- package/library/skills/verification-before-completion/resource.yaml +1 -1
- package/library/skills/verifying-scenes/SKILL.md +31 -2
- package/library/skills/verifying-scenes/resource.yaml +1 -1
- package/library/skills/vfx-dissolve/SKILL.md +21 -2
- package/library/skills/vfx-dissolve/resource.yaml +1 -1
- package/library/skills/vfx-fire/SKILL.md +41 -41
- package/library/skills/vfx-fire/resource.yaml +1 -1
- package/library/skills/vfx-hit-spark/SKILL.md +24 -1
- package/library/skills/vfx-hit-spark/resource.yaml +1 -1
- package/library/skills/vfx-lightning/SKILL.md +30 -16
- package/library/skills/vfx-lightning/resource.yaml +1 -1
- package/library/skills/vfx-magic-glow/SKILL.md +46 -32
- package/library/skills/vfx-magic-glow/resource.yaml +1 -1
- package/library/skills/vfx-muzzle-flash/SKILL.md +43 -35
- package/library/skills/vfx-muzzle-flash/resource.yaml +1 -1
- package/library/skills/vfx-smoke/SKILL.md +39 -31
- package/library/skills/vfx-smoke/resource.yaml +1 -1
- package/library/skills/vfx-water-ripple/SKILL.md +35 -31
- package/library/skills/vfx-water-ripple/resource.yaml +1 -1
- package/library/skills/voice-line/SKILL.md +87 -40
- package/library/skills/voice-line/resource.yaml +1 -1
- package/library/skills/web-and-touch-input/SKILL.md +20 -1
- package/library/skills/web-and-touch-input/resource.yaml +1 -1
- package/library/skills/web-compatibility-look/SKILL.md +21 -1
- package/library/skills/web-compatibility-look/resource.yaml +1 -1
- package/library/skills/world-building-3d/SKILL.md +29 -1
- package/library/skills/world-building-3d/resource.yaml +1 -1
- package/library/skills/writing-skills/resource.yaml +1 -0
- package/library/tools/add-node/GUIDE.md +14 -0
- package/library/tools/align-distribute-3d/GUIDE.md +7 -0
- package/library/tools/app-call/resource.yaml +1 -1
- package/library/tools/app-guide/resource.yaml +2 -2
- package/library/tools/app-page/resource.yaml +1 -1
- package/library/tools/app-screenshot/resource.yaml +19 -1
- package/library/tools/app-tools/resource.yaml +1 -1
- package/library/tools/attach-to-surface/GUIDE.md +15 -0
- package/library/tools/batch/GUIDE.md +27 -0
- package/library/tools/camera-bookmark/GUIDE.md +12 -0
- package/library/tools/capture-gameplay/GUIDE.md +3 -0
- package/library/tools/capture-gameplay/resource.yaml +3 -3
- package/library/tools/check-job/resource.yaml +1 -1
- package/library/tools/clear-console/GUIDE.md +3 -0
- package/library/tools/connect-ports/GUIDE.md +9 -0
- package/library/tools/connect-signal/GUIDE.md +14 -0
- package/library/tools/create-debug-report/GUIDE.md +10 -0
- package/library/tools/create-scene/GUIDE.md +10 -0
- package/library/tools/debug-views/GUIDE.md +7 -0
- package/library/tools/enable-plugin/GUIDE.md +3 -0
- package/library/tools/export-game/GUIDE.md +3 -0
- package/library/tools/export-game/resource.yaml +3 -3
- package/library/tools/export-templates/GUIDE.md +3 -0
- package/library/tools/fabricate-3d/GUIDE.md +19 -0
- package/library/tools/frame-nodes/GUIDE.md +9 -0
- package/library/tools/frame-shot/GUIDE.md +9 -0
- package/library/tools/game-control/GUIDE.md +9 -0
- package/library/tools/game-input/GUIDE.md +9 -0
- package/library/tools/game-probe/GUIDE.md +9 -0
- package/library/tools/generate-3d/resource.yaml +3 -3
- package/library/tools/generate-motion/resource.yaml +2 -2
- package/library/tools/get-console/GUIDE.md +9 -0
- package/library/tools/get-console/resource.yaml +5 -0
- package/library/tools/get-debugger-errors/GUIDE.md +7 -0
- package/library/tools/get-debugger-errors/resource.yaml +5 -0
- package/library/tools/get-debugger-warnings/GUIDE.md +7 -0
- package/library/tools/get-debugger-warnings/resource.yaml +5 -0
- package/library/tools/get-diagnostics/GUIDE.md +13 -0
- package/library/tools/get-project-context/GUIDE.md +25 -0
- package/library/tools/get-runtime-tree/GUIDE.md +7 -0
- package/library/tools/get-runtime-tree/resource.yaml +5 -0
- package/library/tools/get-scene-tree/GUIDE.md +11 -0
- package/library/tools/get-scene-tree/resource.yaml +5 -0
- package/library/tools/get-script-errors/GUIDE.md +5 -0
- package/library/tools/grep/GUIDE.md +11 -0
- package/library/tools/import-asset/GUIDE.md +8 -0
- package/library/tools/import-asset-by-id/GUIDE.md +12 -0
- package/library/tools/import-from-url/GUIDE.md +10 -0
- package/library/tools/import-from-url-batch/GUIDE.md +3 -0
- package/library/tools/import-hdri/GUIDE.md +16 -0
- package/library/tools/input-map-bind/GUIDE.md +11 -0
- package/library/tools/inspect-asset/GUIDE.md +14 -0
- package/library/tools/inspect-asset/resource.yaml +5 -0
- package/library/tools/inspect-node/GUIDE.md +11 -0
- package/library/tools/inspect-node/resource.yaml +5 -0
- package/library/tools/inspect-resource/GUIDE.md +7 -0
- package/library/tools/inspect-resource/resource.yaml +5 -0
- package/library/tools/inspect-runtime-node/GUIDE.md +7 -0
- package/library/tools/inspect-runtime-node/resource.yaml +5 -0
- package/library/tools/instantiate-scene/GUIDE.md +12 -0
- package/library/tools/is-running/GUIDE.md +3 -0
- package/library/tools/measure/GUIDE.md +8 -0
- package/library/tools/navigation-probe/GUIDE.md +9 -0
- package/library/tools/open/resource.yaml +2 -2
- package/library/tools/open-main-scene/GUIDE.md +6 -0
- package/library/tools/open-scene/GUIDE.md +8 -0
- package/library/tools/place-adjacent/GUIDE.md +7 -0
- package/library/tools/play/GUIDE.md +13 -0
- package/library/tools/project-setting/GUIDE.md +9 -0
- package/library/tools/publish-build/resource.yaml +6 -6
- package/library/tools/raycast/GUIDE.md +7 -0
- package/library/tools/read-file/GUIDE.md +10 -0
- package/library/tools/read-library/resource.yaml +5 -0
- package/library/tools/recent-events/GUIDE.md +7 -0
- package/library/tools/remove-node/GUIDE.md +3 -0
- package/library/tools/repeat-along/GUIDE.md +5 -0
- package/library/tools/replace-node/GUIDE.md +9 -0
- package/library/tools/replace-text/GUIDE.md +5 -0
- package/library/tools/run-editor-script/GUIDE.md +16 -0
- package/library/tools/run-script/GUIDE.md +30 -0
- package/library/tools/run-script/resource.yaml +6 -3
- package/library/tools/runtime-animate/GUIDE.md +7 -0
- package/library/tools/runtime-call/GUIDE.md +7 -0
- package/library/tools/runtime-set/GUIDE.md +9 -0
- package/library/tools/runtime-spawn/GUIDE.md +9 -0
- package/library/tools/save-scene/GUIDE.md +3 -0
- package/library/tools/scene-audit/GUIDE.md +29 -0
- package/library/tools/screenshot/GUIDE.md +17 -0
- package/library/tools/select-node/GUIDE.md +3 -0
- package/library/tools/set-prop/GUIDE.md +23 -0
- package/library/tools/set-resource-property/GUIDE.md +8 -0
- package/library/tools/shot-sheet/GUIDE.md +7 -0
- package/library/tools/snap-to-surface/GUIDE.md +17 -0
- package/library/tools/snapshot-diff/GUIDE.md +7 -0
- package/library/tools/starcast/GUIDE.md +12 -0
- package/library/tools/stop/GUIDE.md +3 -0
- package/library/tools/test-placement/GUIDE.md +7 -0
- package/library/tools/ui-actions/GUIDE.md +12 -0
- package/library/tools/ui-activate/GUIDE.md +11 -0
- package/library/tools/ui-screenshot/GUIDE.md +9 -0
- package/library/tools/ui-tree/GUIDE.md +10 -0
- package/library/tools/wait-for-event/GUIDE.md +11 -0
- package/library/tools/world-snapshot/GUIDE.md +9 -0
- package/library/tools/write-file/GUIDE.md +5 -0
- package/library/tools/zoom/GUIDE.md +5 -0
- package/package.json +8 -5
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +6 -13
- package/registry/generated/index.json +482 -349
- package/registry/generated/marketplace.claude.json +2 -13
- package/registry/generated/plugin.claude-directory.json +168 -0
- package/registry/generated/plugin.claude.json +24 -22
- package/registry/generated/plugin.codex.json +18 -22
- package/registry/generated/plugin.cursor.json +8 -14
- package/registry/generated/plugin.factory.json +6 -13
- package/registry/generated/server.json +43 -0
- package/registry/generated/skills-index.md +121 -118
- package/registry/generated/skills-registry.json +167 -115
- package/registry/operations/fixtures/invalid-deprecated-without-replacement.json +60 -0
- package/registry/operations/fixtures/invalid-name.json +60 -0
- package/registry/operations/fixtures/invalid-publish-without-owner.json +82 -0
- package/registry/operations/fixtures/invalid-spend-without-estimate.json +42 -0
- package/registry/operations/fixtures/invalid-unknown-field.json +61 -0
- package/registry/operations/fixtures/valid-player-facing.json +82 -0
- package/registry/operations/fixtures/valid-read.json +60 -0
- package/registry/operations/fixtures/valid-spend.json +43 -0
- package/registry/operations/legacy-names.json +1241 -0
- package/registry/operations/public-catalog.json +33160 -0
- package/registry/schemas/operation.schema.json +318 -0
- package/registry/schemas/skill.schema.json +4 -0
- package/CLAUDE.md +0 -7
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CatalogEntry } from "./catalog.js";
|
|
2
|
+
import { type Approval, type Effect } from "./contract.js";
|
|
3
|
+
export interface PublicOperation {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
effect: Effect;
|
|
8
|
+
approval: Approval;
|
|
9
|
+
permission: string;
|
|
10
|
+
provider: "cloud";
|
|
11
|
+
auth: string;
|
|
12
|
+
status: "stable" | "preview" | "deprecated";
|
|
13
|
+
since: string;
|
|
14
|
+
mcpTool: string;
|
|
15
|
+
direct: string[];
|
|
16
|
+
idempotencyKey?: "required" | "optional";
|
|
17
|
+
job?: {
|
|
18
|
+
typicalSeconds: number;
|
|
19
|
+
};
|
|
20
|
+
input: Record<string, unknown>;
|
|
21
|
+
output: Record<string, unknown>;
|
|
22
|
+
legacy?: string[];
|
|
23
|
+
tags?: string[];
|
|
24
|
+
examples: Array<{
|
|
25
|
+
input: Record<string, unknown>;
|
|
26
|
+
note: string;
|
|
27
|
+
}>;
|
|
28
|
+
deprecated?: {
|
|
29
|
+
replacedBy: string;
|
|
30
|
+
after: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface PublicCatalog {
|
|
34
|
+
contract: number;
|
|
35
|
+
operations: PublicOperation[];
|
|
36
|
+
/** The operations the server leaves to this package (engine and local ones), with their 3.x names. */
|
|
37
|
+
localOperations: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
legacy: string[];
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare function publicCatalog(): PublicCatalog;
|
|
43
|
+
/** The cloud operations as search rows; the ones that save files show `out` in their input. */
|
|
44
|
+
export declare function shippedCloudEntries(): CatalogEntry[];
|
|
45
|
+
/** The catalog's title of an operation, for the local ones it lists too. */
|
|
46
|
+
export declare function shippedTitle(op: string): string | undefined;
|
|
47
|
+
/** One cloud operation as ops.describe answers it, from the shipped catalog; null when it has no such row. */
|
|
48
|
+
export declare function shippedWireForm(op: string): Record<string, unknown> | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PACKAGE_ROOT } from "../package-root.js";
|
|
4
|
+
import { CONTRACT_VERSION, isLocalOp } from "./contract.js";
|
|
5
|
+
import { withOutField } from "./save-files.js";
|
|
6
|
+
/**
|
|
7
|
+
* The hosted Summer server's public operation catalog, shipped in this
|
|
8
|
+
* package: registry/operations/public-catalog.json, a byte copy of the
|
|
9
|
+
* server's own file (`npm run sync:catalog`; `-- --check` fails when it is
|
|
10
|
+
* behind). Search and describe list every cloud operation from it with no
|
|
11
|
+
* sign-in; a signed-in search lays the server's live rows over it.
|
|
12
|
+
*/
|
|
13
|
+
const PUBLIC_CATALOG_FILE = join("registry", "operations", "public-catalog.json");
|
|
14
|
+
let catalogCache = null;
|
|
15
|
+
export function publicCatalog() {
|
|
16
|
+
if (catalogCache)
|
|
17
|
+
return catalogCache;
|
|
18
|
+
try {
|
|
19
|
+
catalogCache = JSON.parse(readFileSync(join(PACKAGE_ROOT, PUBLIC_CATALOG_FILE), "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
catalogCache = { contract: CONTRACT_VERSION, operations: [], localOperations: [] };
|
|
23
|
+
}
|
|
24
|
+
return catalogCache;
|
|
25
|
+
}
|
|
26
|
+
/** The cloud operations as search rows; the ones that save files show `out` in their input. */
|
|
27
|
+
export function shippedCloudEntries() {
|
|
28
|
+
return publicCatalog()
|
|
29
|
+
.operations.filter((op) => !isLocalOp(op.name))
|
|
30
|
+
.map((op) => ({
|
|
31
|
+
name: op.name,
|
|
32
|
+
title: op.title,
|
|
33
|
+
summary: op.summary,
|
|
34
|
+
effect: op.effect,
|
|
35
|
+
approval: op.approval,
|
|
36
|
+
provider: "cloud",
|
|
37
|
+
status: op.status,
|
|
38
|
+
available: true,
|
|
39
|
+
...(op.tags?.length ? { tags: op.tags } : {}),
|
|
40
|
+
...(op.legacy?.length ? { legacy: op.legacy } : {}),
|
|
41
|
+
input: withOutField(op.name, op.input),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** The catalog's title of an operation, for the local ones it lists too. */
|
|
45
|
+
export function shippedTitle(op) {
|
|
46
|
+
return publicCatalog().operations.find((candidate) => candidate.name === op)?.title;
|
|
47
|
+
}
|
|
48
|
+
/** One cloud operation as ops.describe answers it, from the shipped catalog; null when it has no such row. */
|
|
49
|
+
export function shippedWireForm(op) {
|
|
50
|
+
const found = publicCatalog().operations.find((candidate) => candidate.name === op);
|
|
51
|
+
if (!found || isLocalOp(op))
|
|
52
|
+
return null;
|
|
53
|
+
// `direct` names the hosted profiles that list it as a tool; it says nothing here.
|
|
54
|
+
const { direct: _direct, ...wire } = found;
|
|
55
|
+
return { contract: CONTRACT_VERSION, ...wire, input: withOutField(op, found.input), needsSignIn: true };
|
|
56
|
+
}
|
|
@@ -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,8 @@
|
|
|
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 searchEntries(entries: readonly CatalogEntry[], query: string, filters: {
|
|
5
|
+
area?: string;
|
|
6
|
+
effect?: Effect;
|
|
7
|
+
limit: number;
|
|
8
|
+
}): CatalogEntry[];
|