summer-engine 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +5 -3
- package/.cursor-plugin/plugin.json +4 -2
- package/.factory-plugin/plugin.json +4 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +87 -0
- package/README.md +67 -27
- package/commands/start.md +19 -15
- package/commands/summer.md +1 -0
- package/dist/cli/command-usage.d.ts +3 -1
- package/dist/cli/command-usage.js +10 -4
- package/dist/cli/commands/create.d.ts +1 -25
- package/dist/cli/commands/create.js +10 -138
- package/dist/cli/commands/dev.d.ts +66 -0
- package/dist/cli/commands/dev.js +234 -0
- package/dist/cli/commands/install.d.ts +15 -58
- package/dist/cli/commands/install.js +31 -481
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/ops.d.ts +17 -1
- package/dist/cli/commands/ops.js +97 -9
- package/dist/cli/commands/preview.d.ts +58 -0
- package/dist/cli/commands/preview.js +181 -0
- package/dist/cli/commands/run.d.ts +1 -17
- package/dist/cli/commands/run.js +17 -206
- package/dist/cli/index.js +5 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +1 -1
- package/dist/core/api-client.d.ts +10 -2
- package/dist/core/api-client.js +13 -8
- package/dist/core/capabilities/agent-playbook.js +1 -0
- package/dist/core/capabilities/apps.d.ts +9 -9
- package/dist/core/capabilities/asset-import.d.ts +4 -1
- package/dist/core/capabilities/asset-import.js +15 -6
- package/dist/core/capabilities/capture-gameplay.js +1 -1
- package/dist/core/capabilities/connect-signal.d.ts +2 -2
- package/dist/core/capabilities/content-make/index.d.ts +81 -0
- package/dist/core/capabilities/content-make/index.js +706 -0
- package/dist/core/capabilities/content-make/media.d.ts +57 -0
- package/dist/core/capabilities/content-make/media.js +172 -0
- package/dist/core/capabilities/content-make/plan.d.ts +130 -0
- package/dist/core/capabilities/content-make/plan.js +433 -0
- package/dist/core/capabilities/content-make/render.d.ts +64 -0
- package/dist/core/capabilities/content-make/render.js +370 -0
- package/dist/core/capabilities/content-make/study.d.ts +13 -0
- package/dist/core/capabilities/content-make/study.js +165 -0
- package/dist/core/capabilities/content-make/types.d.ts +173 -0
- package/dist/core/capabilities/content-make/types.js +6 -0
- package/dist/core/capabilities/content-make/voice.d.ts +7 -0
- package/dist/core/capabilities/content-make/voice.js +27 -0
- package/dist/core/capabilities/events.d.ts +2 -2
- package/dist/core/capabilities/export-game.d.ts +4 -1
- package/dist/core/capabilities/export-game.js +4 -3
- package/dist/core/capabilities/export-presets.d.ts +2 -1
- package/dist/core/capabilities/export-presets.js +2 -1
- package/dist/core/capabilities/go-live.d.ts +14 -0
- package/dist/core/capabilities/go-live.js +408 -0
- package/dist/core/capabilities/image-generation.d.ts +6 -6
- package/dist/core/capabilities/item-manifest.d.ts +92 -0
- package/dist/core/capabilities/item-manifest.js +247 -0
- package/dist/core/capabilities/placement.d.ts +7 -7
- package/dist/core/capabilities/projects-local.d.ts +68 -0
- package/dist/core/capabilities/projects-local.js +172 -0
- package/dist/core/capabilities/publish-build.d.ts +13 -2
- package/dist/core/capabilities/publish-build.js +87 -21
- package/dist/core/capabilities/publish-download.js +1 -1
- package/dist/core/capabilities/runtime-control.d.ts +7 -2
- package/dist/core/capabilities/runtime-control.js +27 -1
- package/dist/core/capabilities/seeing/args.d.ts +2 -2
- package/dist/core/capabilities/store-checks.d.ts +45 -0
- package/dist/core/capabilities/store-checks.js +85 -0
- package/dist/core/capabilities/store-details.d.ts +37 -0
- package/dist/core/capabilities/store-details.js +94 -0
- package/dist/core/capabilities/summer-bundle.d.ts +21 -2
- package/dist/core/capabilities/summer-bundle.js +54 -7
- package/dist/core/capabilities/sync-items.d.ts +89 -0
- package/dist/core/capabilities/sync-items.js +352 -0
- package/dist/core/capabilities/tool-dispatch.js +66 -6
- package/dist/core/capabilities/video-ogv.d.ts +49 -0
- package/dist/core/capabilities/video-ogv.js +260 -0
- package/dist/core/capability-skew.js +2 -2
- package/dist/core/cli-usage-beacon.js +26 -2
- package/dist/core/cli-usage.d.ts +5 -2
- package/dist/core/cli-usage.js +8 -2
- package/dist/core/engine-install.d.ts +53 -0
- package/dist/core/engine-install.js +93 -0
- package/dist/core/engine-installer.d.ts +134 -0
- package/dist/core/engine-installer.js +760 -0
- package/dist/core/engine-launch.d.ts +126 -0
- package/dist/core/engine-launch.js +398 -0
- package/dist/core/engine-self-start.d.ts +63 -0
- package/dist/core/engine-self-start.js +282 -0
- package/dist/core/engine.d.ts +7 -1
- package/dist/core/engine.js +9 -2
- package/dist/core/error-codes.d.ts +4 -1
- package/dist/core/error-codes.js +7 -3
- package/dist/core/library-read.d.ts +2 -2
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/local-project.d.ts +50 -0
- package/dist/core/local-project.js +234 -0
- package/dist/core/ops/areas.js +9 -3
- package/dist/core/ops/catalog.d.ts +9 -0
- package/dist/core/ops/catalog.js +56 -4
- package/dist/core/ops/engine-answer.d.ts +4 -1
- package/dist/core/ops/engine-answer.js +16 -3
- package/dist/core/ops/engine.js +4 -4
- package/dist/core/ops/save-files.d.ts +6 -1
- package/dist/core/ops/save-files.js +67 -50
- package/dist/core/ops/search.js +9 -2
- package/dist/core/preview/input.d.ts +35 -0
- package/dist/core/preview/input.js +105 -0
- package/dist/core/preview/launch-config.d.ts +52 -0
- package/dist/core/preview/launch-config.js +137 -0
- package/dist/core/preview/page.d.ts +22 -0
- package/dist/core/preview/page.js +232 -0
- package/dist/core/preview/preview-source.d.ts +179 -0
- package/dist/core/preview/preview-source.js +350 -0
- package/dist/core/preview/server.d.ts +26 -0
- package/dist/core/preview/server.js +218 -0
- package/dist/core/telemetry.d.ts +20 -16
- package/dist/core/telemetry.js +92 -16
- package/dist/core/templates.d.ts +20 -0
- package/dist/core/templates.js +300 -3
- package/dist/core/tool-profiles.d.ts +2 -2
- package/dist/core/tool-profiles.js +1 -0
- package/dist/core/usage-fields.d.ts +41 -0
- package/dist/core/usage-fields.js +150 -0
- package/dist/core/util/sleep.d.ts +3 -0
- package/dist/core/util/sleep.js +12 -0
- package/dist/mcp/ops-surface.js +28 -2
- package/dist/mcp/server.d.ts +14 -4
- package/dist/mcp/server.js +42 -8
- package/dist/mcp/tool-surface.d.ts +8 -0
- package/dist/mcp/tool-surface.js +19 -2
- package/dist/mcp/tools/creator-tools.js +92 -14
- package/dist/mcp/tools/debug-tools.js +2 -0
- package/dist/mcp/tools/local-project-tools.d.ts +6 -0
- package/dist/mcp/tools/local-project-tools.js +33 -0
- package/dist/mcp/tools/navigation-tools.js +2 -1
- package/dist/mcp/tools/project-tools.js +5 -8
- package/dist/mcp/tools/seeing-tools.js +1 -1
- package/dist/mcp/tools/visual-tools.js +1 -1
- package/dist/mcp/tools/with-engine.js +1 -1
- package/gemini-extension.json +3 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
- package/library/skills/README.md +12 -8
- package/library/skills/animated-loop/SKILL.md +10 -16
- package/library/skills/cinematic-cutscene/SKILL.md +7 -18
- package/library/skills/headless-scripting/SKILL.md +7 -4
- package/library/skills/in-game-purchases/SKILL.md +25 -8
- package/library/skills/in-game-purchases/resource.yaml +2 -1
- package/library/skills/launch-ready/SKILL.md +351 -0
- package/library/skills/launch-ready/resource.yaml +45 -0
- package/library/skills/make-content/SKILL.md +202 -0
- package/library/skills/make-content/resource.yaml +32 -0
- package/library/skills/make-game/SKILL.md +2 -0
- package/library/skills/make-game/resource.yaml +2 -1
- package/library/skills/navigate-summer/SKILL.md +4 -2
- package/library/skills/new-project/SKILL.md +15 -22
- package/library/skills/new-project/resource.yaml +2 -1
- package/library/skills/painted-25d-world/SKILL.md +366 -0
- package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
- package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
- package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
- package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
- package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
- package/library/skills/painted-25d-world/resource.yaml +39 -0
- package/library/skills/publish-your-game/SKILL.md +13 -14
- package/library/skills/publish-your-game/resource.yaml +4 -3
- package/library/skills/release-checklist/SKILL.md +188 -0
- package/library/skills/release-checklist/resource.yaml +40 -0
- package/library/skills/release-readiness/SKILL.md +1 -1
- package/library/skills/release-readiness/resource.yaml +1 -1
- package/library/skills/store-art/SKILL.md +1 -1
- package/library/skills/store-art/resource.yaml +1 -1
- package/library/skills/store-listing/SKILL.md +1 -1
- package/library/skills/store-listing/resource.yaml +1 -1
- package/library/skills/summer-cli/SKILL.md +2 -0
- package/library/skills/summer-store/SKILL.md +295 -39
- package/library/skills/summer-store/resource.yaml +9 -3
- package/library/skills/trailer-shot/SKILL.md +3 -3
- package/library/templates/README.md +5 -0
- package/library/tools/content-make/resource.yaml +132 -0
- package/library/tools/frame-shot/GUIDE.md +1 -1
- package/library/tools/go-live/resource.yaml +67 -0
- package/library/tools/import-from-url/GUIDE.md +1 -0
- package/library/tools/import-from-url-batch/GUIDE.md +1 -1
- package/library/tools/play/GUIDE.md +2 -0
- package/library/tools/projects-create-local/resource.yaml +55 -0
- package/library/tools/projects-open-local/resource.yaml +51 -0
- package/library/tools/publish-build/resource.yaml +3 -3
- package/library/tools/screenshot/GUIDE.md +1 -1
- package/library/tools/store-set-details/resource.yaml +114 -0
- package/library/tools/sync-items/resource.yaml +72 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +6 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -1
- package/registry/generated/index.json +463 -37
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -2
- package/registry/generated/plugin.claude.json +3 -1
- package/registry/generated/plugin.codex.json +5 -3
- package/registry/generated/plugin.cursor.json +4 -2
- package/registry/generated/plugin.factory.json +4 -2
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +12 -8
- package/registry/generated/skills-registry.json +62 -5
- package/registry/operations/legacy-names.json +40 -0
- package/registry/operations/public-catalog.json +10144 -387
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing a new Summer project to disk: the built-in generators, the pinned
|
|
3
|
+
* template fetch, the autopilot scaffold, `.summer/project.json` and the
|
|
4
|
+
* agent instruction files. Shared by `summer create`, `summer dev` and the
|
|
5
|
+
* MCP's projects.create_local.
|
|
6
|
+
*/
|
|
7
|
+
import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { dirname, join, resolve } from "path";
|
|
10
|
+
import { checkEngineHealth, getApiPort } from "./engine.js";
|
|
11
|
+
import { PACKAGE_ROOT } from "./package-root.js";
|
|
12
|
+
import { SUMMER_ENGINE_COMPATIBILITY } from "./summer-compatibility.js";
|
|
13
|
+
import { fetchPinnedTemplate } from "./templates.js";
|
|
14
|
+
import { noteToolUsage, templateTags } from "./usage-fields.js";
|
|
15
|
+
import { writeProjectManifest } from "../project-memory/project-manifest.js";
|
|
16
|
+
import { writeAgentInstructions } from "../project-memory/agent-instructions.js";
|
|
17
|
+
import { TOOLKIT_VERSION as toolkitVersion } from "./version.js";
|
|
18
|
+
/** Summer Engine's version when it is reachable right now, else undefined —
|
|
19
|
+
* the manifest then omits engine_version rather than guessing. */
|
|
20
|
+
async function detectEngineVersion() {
|
|
21
|
+
try {
|
|
22
|
+
return (await checkEngineHealth(await getApiPort()))?.version;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const BUILTIN_GENERATORS = {
|
|
29
|
+
empty: (dir, name) => {
|
|
30
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
31
|
+
writeFileSync(join(dir, "main.tscn"), emptyScene());
|
|
32
|
+
},
|
|
33
|
+
"3d-basic": (dir, name) => {
|
|
34
|
+
writeFileSync(join(dir, "project.godot"), renderProjectSettings(name, "res://main.tscn"));
|
|
35
|
+
writeFileSync(join(dir, "main.tscn"), basicScene3D());
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Copy the autopilot verification scaffold into a fresh project.
|
|
40
|
+
*
|
|
41
|
+
* Every project gets a working example of how to prove its own gameplay: a probe
|
|
42
|
+
* that drives the player with real input, saves rendered frames, asserts, and
|
|
43
|
+
* exits. Without it every agent reinvents verification from scratch, and most
|
|
44
|
+
* settle for asking the user to play the game instead.
|
|
45
|
+
*
|
|
46
|
+
* Never overwrites — an existing tests/autopilot/ is the user's, not ours.
|
|
47
|
+
*/
|
|
48
|
+
export function scaffoldAutopilot(projectDir) {
|
|
49
|
+
const source = join(PACKAGE_ROOT, "assets", "autopilot");
|
|
50
|
+
const target = join(projectDir, "tests", "autopilot");
|
|
51
|
+
if (!existsSync(source) || existsSync(target))
|
|
52
|
+
return false;
|
|
53
|
+
cpSync(source, target, { recursive: true });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
/** A folder `summer dev` may create a project in: missing, empty, or only
|
|
57
|
+
* dotfiles (.git, .claude, .vscode, ...). */
|
|
58
|
+
export function isEmptyProjectFolder(dir) {
|
|
59
|
+
if (!existsSync(dir))
|
|
60
|
+
return true;
|
|
61
|
+
if (!statSync(dir).isDirectory())
|
|
62
|
+
return false;
|
|
63
|
+
// .summer is Summer's own folder (in the home folder, its config): never a place to create in.
|
|
64
|
+
return readdirSync(dir).every((name) => name.startsWith(".") && name !== ".summer");
|
|
65
|
+
}
|
|
66
|
+
/** The home folder or a filesystem root: never a project folder by default. */
|
|
67
|
+
export function isHomeOrRoot(dir, home = homedir()) {
|
|
68
|
+
const full = resolve(dir);
|
|
69
|
+
return full === resolve(home) || full === resolve(full, "/");
|
|
70
|
+
}
|
|
71
|
+
/** The project that holds `dir` (project.godot here or above, never above the home folder), or null. */
|
|
72
|
+
export function projectRootAbove(dir, home = homedir()) {
|
|
73
|
+
let current = resolve(dir);
|
|
74
|
+
while (true) {
|
|
75
|
+
if (existsSync(join(current, "project.godot")))
|
|
76
|
+
return current;
|
|
77
|
+
const parent = dirname(current);
|
|
78
|
+
if (parent === current || current === resolve(home))
|
|
79
|
+
return null;
|
|
80
|
+
current = parent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Write a project from a template into `fullPath`: generate a built-in, or
|
|
85
|
+
* fetch a pinned template (verified tree digest), then the autopilot
|
|
86
|
+
* scaffold, `.summer/project.json` and the agent instruction files.
|
|
87
|
+
* `fullPath` may already exist when isEmptyProjectFolder says so; files that
|
|
88
|
+
* are already there are kept, never replaced.
|
|
89
|
+
*/
|
|
90
|
+
export async function createProject(entry, fullPath, options) {
|
|
91
|
+
const { log } = options;
|
|
92
|
+
// The usage line names the catalog template and its library domains, never the project name or folder.
|
|
93
|
+
noteToolUsage({ templateId: entry.slug, templateTags: templateTags(entry.slug) });
|
|
94
|
+
if (entry.builtin) {
|
|
95
|
+
const generate = BUILTIN_GENERATORS[entry.slug];
|
|
96
|
+
if (!generate)
|
|
97
|
+
throw new Error(`${entry.id} is declared builtin but this CLI has no generator for it. Update the CLI.`);
|
|
98
|
+
log(`Creating project from built-in '${entry.slug}' template...`);
|
|
99
|
+
mkdirSync(fullPath, { recursive: true });
|
|
100
|
+
generate(fullPath, options.name);
|
|
101
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
102
|
+
writeProjectManifest(fullPath, {
|
|
103
|
+
template: { id: entry.id, version: entry.version, builtin: true },
|
|
104
|
+
toolkit_version: toolkitVersion,
|
|
105
|
+
engine_version: await detectEngineVersion(),
|
|
106
|
+
});
|
|
107
|
+
return { scaffolded, instructions: writeAgentInstructions(fullPath) };
|
|
108
|
+
}
|
|
109
|
+
const pin = entry.pin;
|
|
110
|
+
// The fetch needs a folder that does not exist yet: into an existing
|
|
111
|
+
// (empty) folder, fetch next to its files and move them up.
|
|
112
|
+
const staging = existsSync(fullPath) ? join(fullPath, `.summer-create-${process.pid}`) : null;
|
|
113
|
+
const materialized = await fetchPinnedTemplate(entry, { targetDir: staging ?? fullPath, keepGit: options.keepGit, log });
|
|
114
|
+
if (staging) {
|
|
115
|
+
for (const name of readdirSync(staging)) {
|
|
116
|
+
const mine = join(fullPath, name);
|
|
117
|
+
if (!existsSync(mine))
|
|
118
|
+
renameSync(join(staging, name), mine);
|
|
119
|
+
else if (name === ".gitignore") {
|
|
120
|
+
// Keep both: the template's lines (.godot/ and the rest) after the folder's own.
|
|
121
|
+
const have = new Set(readFileSync(mine, "utf8").split(/\r?\n/));
|
|
122
|
+
const add = readFileSync(join(staging, name), "utf8").split(/\r?\n/).filter((line) => line.trim() && !have.has(line));
|
|
123
|
+
if (add.length)
|
|
124
|
+
appendFileSync(mine, `\n# From the ${entry.slug} template\n${add.join("\n")}\n`);
|
|
125
|
+
log(`Added the template's .gitignore lines to the existing .gitignore.`);
|
|
126
|
+
}
|
|
127
|
+
else
|
|
128
|
+
log(`Kept the existing ${name}; the template's ${name} was not used.`);
|
|
129
|
+
}
|
|
130
|
+
rmSync(staging, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
const scaffolded = scaffoldAutopilot(fullPath);
|
|
133
|
+
writeProjectManifest(fullPath, {
|
|
134
|
+
template: {
|
|
135
|
+
id: entry.id,
|
|
136
|
+
version: entry.version,
|
|
137
|
+
repo: pin.repo,
|
|
138
|
+
commit: materialized.commit,
|
|
139
|
+
tree_digest: materialized.tree_digest,
|
|
140
|
+
},
|
|
141
|
+
toolkit_version: toolkitVersion,
|
|
142
|
+
engine_version: await detectEngineVersion(),
|
|
143
|
+
});
|
|
144
|
+
log(`Source: ${pin.repo} @ ${materialized.commit.slice(0, 12)} (tree digest verified)`);
|
|
145
|
+
return { scaffolded, instructions: writeAgentInstructions(fullPath) };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* project.godot uses C-style double-quoted strings; a project name containing
|
|
149
|
+
* `"` or `\` must be escaped or the file does not parse.
|
|
150
|
+
*/
|
|
151
|
+
export function escapeGodotString(value) {
|
|
152
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, "\\n");
|
|
153
|
+
}
|
|
154
|
+
export function renderProjectSettings(name, mainScene) {
|
|
155
|
+
return `; Summer Engine Project
|
|
156
|
+
; Generated by summer-engine CLI
|
|
157
|
+
; Technical base ${SUMMER_ENGINE_COMPATIBILITY.currentTechnicalBaseVersion}; Summer follows upstream continuously
|
|
158
|
+
|
|
159
|
+
[application]
|
|
160
|
+
|
|
161
|
+
config/name="${escapeGodotString(name)}"
|
|
162
|
+
run/main_scene="${escapeGodotString(mainScene)}"
|
|
163
|
+
config/features=PackedStringArray("${SUMMER_ENGINE_COMPATIBILITY.projectFeatureTag}")
|
|
164
|
+
|
|
165
|
+
[rendering]
|
|
166
|
+
|
|
167
|
+
renderer/rendering_method="forward_plus"
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
function emptyScene() {
|
|
171
|
+
return `[gd_scene format=3]
|
|
172
|
+
|
|
173
|
+
[node name="World" type="Node3D"]
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
/** A small scene that already looks like a game: procedural sky on a real
|
|
177
|
+
* Environment (ACES, sky ambient light), a sun with soft shadows, a tinted
|
|
178
|
+
* ground and one lit hero, framed by the camera. */
|
|
179
|
+
function basicScene3D() {
|
|
180
|
+
return `[gd_scene load_steps=8 format=3]
|
|
181
|
+
|
|
182
|
+
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
|
183
|
+
sky_top_color = Color(0.25, 0.45, 0.8, 1)
|
|
184
|
+
sky_horizon_color = Color(0.7, 0.78, 0.88, 1)
|
|
185
|
+
ground_bottom_color = Color(0.16, 0.17, 0.15, 1)
|
|
186
|
+
ground_horizon_color = Color(0.7, 0.78, 0.88, 1)
|
|
187
|
+
|
|
188
|
+
[sub_resource type="Sky" id="Sky_main"]
|
|
189
|
+
sky_material = SubResource("ProceduralSkyMaterial_sky")
|
|
190
|
+
|
|
191
|
+
[sub_resource type="Environment" id="Environment_main"]
|
|
192
|
+
background_mode = 2
|
|
193
|
+
sky = SubResource("Sky_main")
|
|
194
|
+
ambient_light_source = 3
|
|
195
|
+
reflected_light_source = 2
|
|
196
|
+
tonemap_mode = 3
|
|
197
|
+
|
|
198
|
+
[sub_resource type="PlaneMesh" id="PlaneMesh_ground"]
|
|
199
|
+
size = Vector2(200, 200)
|
|
200
|
+
|
|
201
|
+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ground"]
|
|
202
|
+
albedo_color = Color(0.35, 0.52, 0.3, 1)
|
|
203
|
+
roughness = 0.95
|
|
204
|
+
|
|
205
|
+
[sub_resource type="CapsuleMesh" id="CapsuleMesh_hero"]
|
|
206
|
+
|
|
207
|
+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hero"]
|
|
208
|
+
albedo_color = Color(0.95, 0.45, 0.2, 1)
|
|
209
|
+
roughness = 0.45
|
|
210
|
+
|
|
211
|
+
[node name="World" type="Node3D"]
|
|
212
|
+
|
|
213
|
+
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
|
214
|
+
environment = SubResource("Environment_main")
|
|
215
|
+
|
|
216
|
+
[node name="Sun" type="DirectionalLight3D" parent="."]
|
|
217
|
+
transform = Transform3D(0.536875, -0.636738, 0.55347, 0, 0.656033, 0.754732, -0.843661, -0.405197, 0.352208, 0, 10, 0)
|
|
218
|
+
light_angular_distance = 0.5
|
|
219
|
+
shadow_enabled = true
|
|
220
|
+
directional_shadow_max_distance = 40.0
|
|
221
|
+
|
|
222
|
+
[node name="Ground" type="MeshInstance3D" parent="."]
|
|
223
|
+
mesh = SubResource("PlaneMesh_ground")
|
|
224
|
+
surface_material_override/0 = SubResource("StandardMaterial3D_ground")
|
|
225
|
+
|
|
226
|
+
[node name="Hero" type="MeshInstance3D" parent="."]
|
|
227
|
+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
228
|
+
mesh = SubResource("CapsuleMesh_hero")
|
|
229
|
+
surface_material_override/0 = SubResource("StandardMaterial3D_hero")
|
|
230
|
+
|
|
231
|
+
[node name="Camera3D" type="Camera3D" parent="."]
|
|
232
|
+
transform = Transform3D(0.810679, -0.128625, 0.571187, 0, 0.97557, 0.219687, -0.585491, -0.178096, 0.790875, 2.6, 1.9, 3.6)
|
|
233
|
+
`;
|
|
234
|
+
}
|
package/dist/core/ops/areas.js
CHANGED
|
@@ -15,10 +15,16 @@ export const CLI_AREAS = [
|
|
|
15
15
|
/**
|
|
16
16
|
* Operations that run in this package: everything in `engine`, the library
|
|
17
17
|
* (`docs`, apart from the Studio workflows, which are cloud), opening a
|
|
18
|
-
* page or an editor surface, docking an App in the editor,
|
|
19
|
-
* local export
|
|
18
|
+
* page or an editor surface, docking an App in the editor, uploading a
|
|
19
|
+
* local export, taking a store game live and filling its store page
|
|
20
|
+
* sections (store.go_live, store.set_details), syncing the project's
|
|
21
|
+
* summer.items.json, and making a
|
|
22
|
+
* piece of content from footage on this computer (content.make; Summer's
|
|
23
|
+
* server keeps the request it reports to), and creating and opening a
|
|
24
|
+
* project on this computer (projects.create_local, projects.open_local).
|
|
25
|
+
* Everything else runs on the hosted server.
|
|
20
26
|
*/
|
|
21
|
-
const LOCAL_OPS = new Set(["docs.search", "docs.read", "docs.feedback", "docs.plan", "docs.engine_api", "studio.open", "apps.dock", "apps.publish_folder", "store.upload_file"]);
|
|
27
|
+
const LOCAL_OPS = new Set(["docs.search", "docs.read", "docs.feedback", "docs.plan", "docs.engine_api", "studio.open", "apps.dock", "apps.publish_folder", "store.upload_file", "store.go_live", "store.set_details", "economy.sync_items", "content.make", "projects.create_local", "projects.open_local"]);
|
|
22
28
|
export function isLocalOp(op) {
|
|
23
29
|
return op.startsWith("engine.") || LOCAL_OPS.has(op);
|
|
24
30
|
}
|
|
@@ -43,6 +43,15 @@ export declare function localInputSchema(op: string): Record<string, unknown> |
|
|
|
43
43
|
* leaves the machine as an operation name.
|
|
44
44
|
*/
|
|
45
45
|
export declare function knownOp(op: string | null | undefined): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* The operation `summer <area> <action>` runs. Agents often type a 3.x tool
|
|
48
|
+
* name as the action (`summer engine summer_get_scene_tree`, `summer engine
|
|
49
|
+
* camera-bookmark`, `summer engine search_library`): those resolve through
|
|
50
|
+
* legacy-names.json, so a 3.x name runs instead of failing not_found.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveCliOp(area: string, action: string): string;
|
|
53
|
+
/** The area's real actions nearest to what was typed, nearest first (kebab-case, as typed on the CLI). */
|
|
54
|
+
export declare function closestActions(op: string, limit?: number): string[];
|
|
46
55
|
export declare function localWireForm(op: string): Record<string, unknown> | null;
|
|
47
56
|
/**
|
|
48
57
|
* The first problem a JSON Schema's top level finds in an input: a missing
|
package/dist/core/ops/catalog.js
CHANGED
|
@@ -3,9 +3,9 @@ import { join } from "node:path";
|
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { loadLibraryIndex } from "../library-search.js";
|
|
5
5
|
import { PACKAGE_ROOT } from "../package-root.js";
|
|
6
|
-
import { CONTRACT_VERSION, isLocalOp, legacyNames, LOCAL_RUNNERS, mcpNameOf, MIN_APPROVAL } from "./contract.js";
|
|
6
|
+
import { CONTRACT_VERSION, isLocalOp, legacyNames, LOCAL_RUNNERS, mcpNameOf, MIN_APPROVAL, opFromCli, resolveOpName } from "./contract.js";
|
|
7
7
|
import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, splitInputSchema } from "./engine.js";
|
|
8
|
-
import { shippedTitle } from "./public-catalog.js";
|
|
8
|
+
import { shippedCloudEntries, shippedTitle } from "./public-catalog.js";
|
|
9
9
|
/** A local tool's effect from its registry authority: scene edits and project files are writes. */
|
|
10
10
|
function effectOf(authority) {
|
|
11
11
|
if (authority?.publish)
|
|
@@ -19,11 +19,23 @@ const titleOf = (summary, op) => {
|
|
|
19
19
|
return sentence && sentence.length <= 60 ? sentence : op.replace(/^[a-z]+\./, "").replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
|
|
20
20
|
};
|
|
21
21
|
let entriesCache = null;
|
|
22
|
+
/**
|
|
23
|
+
* The local operations that reach Summer with the person's sign-in (the
|
|
24
|
+
* account and the store sign-in `summer login` makes; `summer login --store`
|
|
25
|
+
* for the store sign-in alone). Every other local one works signed out.
|
|
26
|
+
*/
|
|
27
|
+
const SIGNED_IN_LOCAL_OPS = new Set(["economy.sync_items", "store.upload_file", "store.go_live", "store.set_details", "apps.publish_folder", "content.make"]);
|
|
28
|
+
/**
|
|
29
|
+
* Local operations that change Summer's records although their registry
|
|
30
|
+
* authority names no local file or publish: store.set_details saves the store
|
|
31
|
+
* page draft (players see nothing until it goes live).
|
|
32
|
+
*/
|
|
33
|
+
const REMOTE_WRITE_LOCAL_OPS = new Set(["store.set_details"]);
|
|
22
34
|
function entryFor(op, toolName, tool, legacy) {
|
|
23
35
|
// Engine operations carry their own title, summary and effect (engine.ts);
|
|
24
36
|
// other local operations fall back to the descriptor.
|
|
25
37
|
const def = engineOpDef(op);
|
|
26
|
-
const effect = def?.effect ?? effectOf(tool.authority);
|
|
38
|
+
const effect = def?.effect ?? (REMOTE_WRITE_LOCAL_OPS.has(op) ? "write" : effectOf(tool.authority));
|
|
27
39
|
const summary = def?.summary ?? (tool.summary ?? "").slice(0, 300);
|
|
28
40
|
return {
|
|
29
41
|
name: op,
|
|
@@ -118,6 +130,46 @@ export function knownOp(op) {
|
|
|
118
130
|
return null;
|
|
119
131
|
return localEntries().some((entry) => entry.name === op) || shippedTitle(op) !== undefined ? op : null;
|
|
120
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The operation `summer <area> <action>` runs. Agents often type a 3.x tool
|
|
135
|
+
* name as the action (`summer engine summer_get_scene_tree`, `summer engine
|
|
136
|
+
* camera-bookmark`, `summer engine search_library`): those resolve through
|
|
137
|
+
* legacy-names.json, so a 3.x name runs instead of failing not_found.
|
|
138
|
+
*/
|
|
139
|
+
export function resolveCliOp(area, action) {
|
|
140
|
+
const op = opFromCli(area, action);
|
|
141
|
+
if (knownOp(op))
|
|
142
|
+
return op;
|
|
143
|
+
const [cleanArea, word] = op.split(".");
|
|
144
|
+
const bare = word.replace(/^summer_/, "").replace(new RegExp(`^${cleanArea}_`), "");
|
|
145
|
+
for (const candidate of [`${cleanArea}.${bare}`, resolveOpName(`summer_${bare}`), resolveOpName(word)]) {
|
|
146
|
+
if (knownOp(candidate))
|
|
147
|
+
return candidate;
|
|
148
|
+
}
|
|
149
|
+
return op;
|
|
150
|
+
}
|
|
151
|
+
function editDistance(a, b) {
|
|
152
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
153
|
+
for (let i = 1; i <= a.length; i++) {
|
|
154
|
+
const current = [i];
|
|
155
|
+
for (let j = 1; j <= b.length; j++) {
|
|
156
|
+
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
157
|
+
}
|
|
158
|
+
previous = current;
|
|
159
|
+
}
|
|
160
|
+
return previous[b.length];
|
|
161
|
+
}
|
|
162
|
+
/** The area's real actions nearest to what was typed, nearest first (kebab-case, as typed on the CLI). */
|
|
163
|
+
export function closestActions(op, limit = 3) {
|
|
164
|
+
const [area, action = ""] = op.split(".");
|
|
165
|
+
const typed = action.replace(/^summer_/, "");
|
|
166
|
+
const actions = new Set([...localEntries(), ...shippedCloudEntries()].filter((entry) => entry.name.startsWith(`${area}.`)).map((entry) => entry.name.slice(area.length + 1)));
|
|
167
|
+
const score = (candidate) => (candidate.includes(typed) || typed.includes(candidate) ? 0 : editDistance(typed, candidate));
|
|
168
|
+
return [...actions]
|
|
169
|
+
.sort((a, b) => score(a) - score(b) || a.localeCompare(b))
|
|
170
|
+
.slice(0, limit)
|
|
171
|
+
.map((candidate) => candidate.replace(/_/g, "-"));
|
|
172
|
+
}
|
|
121
173
|
export function localWireForm(op) {
|
|
122
174
|
const entry = localEntries().find((candidate) => candidate.name === op);
|
|
123
175
|
if (!entry)
|
|
@@ -135,7 +187,7 @@ export function localWireForm(op) {
|
|
|
135
187
|
effect: entry.effect,
|
|
136
188
|
approval: entry.approval,
|
|
137
189
|
provider: entry.provider,
|
|
138
|
-
auth: def?.auth ?? "none",
|
|
190
|
+
auth: def?.auth ?? (SIGNED_IN_LOCAL_OPS.has(op) ? "account" : "none"),
|
|
139
191
|
input: localInputSchema(op) ?? { type: "object" },
|
|
140
192
|
output: { type: "object" },
|
|
141
193
|
...(legacy.length ? { legacy } : {}),
|
|
@@ -7,7 +7,8 @@ import { type Answer, type NextStep } from "./contract.js";
|
|
|
7
7
|
* `details.tool`, so nothing the 3.x result said is lost.
|
|
8
8
|
*/
|
|
9
9
|
export declare const ENGINE_NOT_RUNNING_STEP: NextStep;
|
|
10
|
-
|
|
10
|
+
/** `recovery`: the 3.x result's own next step (not installed, no project, still starting, ...). */
|
|
11
|
+
export declare function engineNotRunning(recovery?: string): Answer;
|
|
11
12
|
/** The text blocks of a tool result: the first is its payload, the rest are notes (a tip, a caption). */
|
|
12
13
|
export declare function textsOf(result: unknown): string[];
|
|
13
14
|
/**
|
|
@@ -18,6 +19,8 @@ export declare function engineFailure(text: string, op: string, options: {
|
|
|
18
19
|
effect: "read" | "write";
|
|
19
20
|
guide?: string;
|
|
20
21
|
}): Answer;
|
|
22
|
+
/** A local tool's coded failure (`{ok:false, code, error, recovery}`) as the answer, with its recovery as the next step; null when it has no such code. */
|
|
23
|
+
export declare function codedLocalFailure(body: Record<string, unknown>): Answer | null;
|
|
21
24
|
/** A 3.x engine tool's whole result as the answer: its payload on success, the mapped failure otherwise. */
|
|
22
25
|
export declare function answerOfEngineResult(result: unknown, op: string, options: {
|
|
23
26
|
effect: "read" | "write";
|
|
@@ -9,8 +9,9 @@ import { errorAnswer } from "./contract.js";
|
|
|
9
9
|
export const ENGINE_NOT_RUNNING_STEP = {
|
|
10
10
|
say: "Open the project in Summer Engine, or run: npx -y summer-engine@latest run <project-path>",
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/** `recovery`: the 3.x result's own next step (not installed, no project, still starting, ...). */
|
|
13
|
+
export function engineNotRunning(recovery) {
|
|
14
|
+
return errorAnswer("engine_not_running", "Summer Engine is not running, or no project is open.", { next: recovery ? { say: recovery } : ENGINE_NOT_RUNNING_STEP });
|
|
14
15
|
}
|
|
15
16
|
/** The text blocks of a tool result: the first is its payload, the rest are notes (a tip, a caption). */
|
|
16
17
|
export function textsOf(result) {
|
|
@@ -64,7 +65,7 @@ export function engineFailure(text, op, options) {
|
|
|
64
65
|
const code = str(body?.code);
|
|
65
66
|
const reason = str(body?.failure_reason);
|
|
66
67
|
if (code === "engine_not_running" || NOT_RUNNING.test(text))
|
|
67
|
-
return engineNotRunning();
|
|
68
|
+
return engineNotRunning(code === "engine_not_running" ? str(body?.recovery) : undefined);
|
|
68
69
|
if (SEVERAL_EDITORS.test(text))
|
|
69
70
|
return errorAnswer("conflict", message, { next: SEVERAL_EDITORS_STEP, details });
|
|
70
71
|
if (NO_PROJECT.test(text))
|
|
@@ -106,6 +107,18 @@ export function engineFailure(text, op, options) {
|
|
|
106
107
|
return errorAnswer("not_found", message, { details });
|
|
107
108
|
return errorAnswer("invalid_input", message, { next: describe, details });
|
|
108
109
|
}
|
|
110
|
+
/** The codes a local tool's failure keeps as they are (projects.create_local, projects.open_local). */
|
|
111
|
+
const LOCAL_CODES = new Set(["engine_not_running", "invalid_input", "not_found", "unavailable", "conflict", "unsupported"]);
|
|
112
|
+
/** A local tool's coded failure (`{ok:false, code, error, recovery}`) as the answer, with its recovery as the next step; null when it has no such code. */
|
|
113
|
+
export function codedLocalFailure(body) {
|
|
114
|
+
const code = str(body.code);
|
|
115
|
+
if (body.ok !== false || !code || !LOCAL_CODES.has(code))
|
|
116
|
+
return null;
|
|
117
|
+
const recovery = str(body.recovery);
|
|
118
|
+
if (code === "engine_not_running")
|
|
119
|
+
return engineNotRunning(recovery);
|
|
120
|
+
return errorAnswer(code, str(body.error) ?? str(body.message) ?? "The operation failed.", { ...(recovery ? { next: { say: recovery } } : {}), details: { tool: body } });
|
|
121
|
+
}
|
|
109
122
|
/** A 3.x engine tool's whole result as the answer: its payload on success, the mapped failure otherwise. */
|
|
110
123
|
export function answerOfEngineResult(result, op, options) {
|
|
111
124
|
const [first = "", ...notes] = textsOf(result);
|
package/dist/core/ops/engine.js
CHANGED
|
@@ -270,7 +270,7 @@ export const ENGINE_OPS = {
|
|
|
270
270
|
// ── Play, stop, runtime ───────────────────────────────────────────────────
|
|
271
271
|
"engine.play": {
|
|
272
272
|
title: "Play the game",
|
|
273
|
-
summary: "
|
|
273
|
+
summary: "Play the game in the editor's real window (agents may; quiet: the editor keeps its tab and focus): main scene or a given one, embedded or a hidden offscreen instance, optional seed and fixed frame rate, or local multiplayer (players). Next: engine.is_running, engine.get_diagnostics.",
|
|
274
274
|
effect: "write",
|
|
275
275
|
example: { input: { instance: "a", mode: "offscreen", deterministic: true, seed: 7 }, note: "A hidden, repeatable test run." },
|
|
276
276
|
},
|
|
@@ -352,7 +352,7 @@ export const ENGINE_OPS = {
|
|
|
352
352
|
// ── Seeing ────────────────────────────────────────────────────────────────
|
|
353
353
|
"engine.screenshot": {
|
|
354
354
|
title: "Take a screenshot",
|
|
355
|
-
summary: "Return a frame as an image you see: the editor viewport, an offscreen render of a scene (preset, camera, bookmark or free pose, numbered marks) or the running game. A blank viewport frame means no redraw yet: capture again.",
|
|
355
|
+
summary: "Return a frame as an image you see, to check your work by eye: the editor viewport, an offscreen render of a scene (preset, camera, bookmark or free pose, numbered marks) or the running game. A blank viewport frame means no redraw yet: capture again.",
|
|
356
356
|
effect: "read",
|
|
357
357
|
example: { input: { target: "scene", scenePath: MAIN, framing: "camera" }, note: "The level through its own camera and lights." },
|
|
358
358
|
},
|
|
@@ -385,7 +385,7 @@ export const ENGINE_OPS = {
|
|
|
385
385
|
},
|
|
386
386
|
"engine.frame_shot": {
|
|
387
387
|
title: "Find camera poses for a shot",
|
|
388
|
-
summary: "Find, score and save camera poses for a shot type (establishing, eye level, low angle, detail, corridor), tested against the real geometry. Returns the best 3 poses and a sheet, and saves the best as a bookmark.",
|
|
388
|
+
summary: "Find, score and save camera poses for a shot type (establishing, eye level, low angle, detail, corridor), tested against the real geometry in the open editor. Returns the best 3 poses and a sheet to look at, and saves the best as a bookmark.",
|
|
389
389
|
effect: "write",
|
|
390
390
|
example: { input: { scenePath: MAIN, shot: "establishing", subject: ["Town"] }, note: "A wide shot of the town." },
|
|
391
391
|
},
|
|
@@ -638,7 +638,7 @@ export function splitInputSchema(def, toolSchema) {
|
|
|
638
638
|
additionalProperties: false,
|
|
639
639
|
};
|
|
640
640
|
}
|
|
641
|
-
const OP_REF = /\b(engine|docs|store)\.[a-z][a-z0-9_]*\b/g;
|
|
641
|
+
const OP_REF = /\b(engine|docs|store|projects)\.[a-z][a-z0-9_]*\b/g;
|
|
642
642
|
/** Every operation a summary names (`engine.snapshot_diff`, `docs.read`). */
|
|
643
643
|
export function opsNamedIn(text) {
|
|
644
644
|
return [...new Set(text.match(OP_REF) ?? [])];
|
|
@@ -73,6 +73,11 @@ export declare class FileSaver {
|
|
|
73
73
|
private targetOf;
|
|
74
74
|
/** Download one file into the target under a free name; notes what changed from the caller's file name. */
|
|
75
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
76
|
private fetchToTemp;
|
|
78
77
|
}
|
|
78
|
+
/** The file in a hidden temp file in `dir` (engines skip dot files), from a public https host, within the size limit. */
|
|
79
|
+
export declare function fetchPublicToTemp(url: string, dir: string, options?: Pick<FileSaverOptions, "fetch" | "lookup" | "maxBytes">): Promise<{
|
|
80
|
+
temp: string;
|
|
81
|
+
finalUrl: URL;
|
|
82
|
+
contentType?: string;
|
|
83
|
+
}>;
|