summer-engine 4.2.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 +98 -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 +10 -0
- package/dist/cli/command-usage.js +28 -0
- 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 +2 -2
- package/dist/cli/commands/ops.d.ts +17 -1
- package/dist/cli/commands/ops.js +102 -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/commands/tool.js +4 -0
- package/dist/cli/index.js +7 -0
- package/dist/core/agent-host.d.ts +6 -0
- package/dist/core/agent-host.js +9 -3
- 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.d.ts +1 -0
- package/dist/core/cli-usage-beacon.js +42 -0
- package/dist/core/cli-usage.d.ts +52 -0
- package/dist/core/cli-usage.js +114 -0
- 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/feedback/client.d.ts +1 -1
- package/dist/core/feedback/client.js +3 -2
- package/dist/core/hosted-mcp.d.ts +7 -1
- package/dist/core/hosted-mcp.js +6 -4
- 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 +16 -0
- package/dist/core/ops/catalog.js +67 -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 +29 -15
- package/dist/core/telemetry.js +144 -39
- 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 +47 -12
- 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/multiplayer-publish/SKILL.md +0 -5
- 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 +464 -38
- 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
package/dist/cli/commands/ops.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { CLI_AREAS } from "../../core/ops/areas.js";
|
|
3
|
-
import { localEntries, localInputSchema, localWireForm, missingInput } from "../../core/ops/catalog.js";
|
|
3
|
+
import { closestActions, localEntries, localInputSchema, localWireForm, missingInput, resolveCliOp } from "../../core/ops/catalog.js";
|
|
4
|
+
import { shippedCloudEntries, shippedWireForm } from "../../core/ops/public-catalog.js";
|
|
4
5
|
import { answerOfHosted, callHostedV4, describeFromShipped, hostedCallFailure, searchEverywhere } from "../../core/ops/cloud.js";
|
|
5
|
-
import { errorAnswer, isLocalOp, localToolsFor,
|
|
6
|
+
import { errorAnswer, isLocalOp, localToolsFor, OP_NAME, resolveOpName } from "../../core/ops/contract.js";
|
|
6
7
|
import { ENGINE_SPLIT_OPS, engineOpDef, splitToolArgs } from "../../core/ops/engine.js";
|
|
7
|
-
import { engineFailure, engineNotRunning } from "../../core/ops/engine-answer.js";
|
|
8
|
+
import { codedLocalFailure, engineFailure, engineNotRunning } from "../../core/ops/engine-answer.js";
|
|
8
9
|
import { loadLibraryIndex } from "../../core/library-search.js";
|
|
9
10
|
import { FileSaver, withOutField } from "../../core/ops/save-files.js";
|
|
10
11
|
import { dispatchTool, EngineUnavailableError, ToolResultError } from "../../core/capabilities/tool-dispatch.js";
|
|
12
|
+
import { noteCliError, noteCliHosted } from "../../core/cli-usage.js";
|
|
11
13
|
/**
|
|
12
14
|
* The CLI view of the operation catalog (on by default; SUMMER_SURFACE=v3 leaves it out):
|
|
13
15
|
*
|
|
@@ -112,8 +114,12 @@ async function runLocal(op, call, deps) {
|
|
|
112
114
|
const split = ENGINE_SPLIT_OPS[op];
|
|
113
115
|
const entry = localEntries().find((candidate) => candidate.name === op);
|
|
114
116
|
const tool = split?.tool ?? entry?.mcpTool ?? localToolsFor(op)[0];
|
|
115
|
-
if (!tool)
|
|
116
|
-
|
|
117
|
+
if (!tool) {
|
|
118
|
+
const [area] = op.split(".");
|
|
119
|
+
const closest = closestActions(op).map((action) => `summer ${area} ${action}`);
|
|
120
|
+
const hint = closest.length ? ` Closest: ${closest.join(", ")}. All of them: summer ${area} --help` : "";
|
|
121
|
+
return errorAnswer("not_found", `No operation is named "${op}".${hint}`, { next: { op: "ops.search", input: { query: op } } });
|
|
122
|
+
}
|
|
117
123
|
// maxCharacters cuts the text an MCP client gets; the CLI prints the whole answer.
|
|
118
124
|
const { maxCharacters, ...input } = call.input;
|
|
119
125
|
const uncut = maxCharacters === undefined ? undefined : ["maxCharacters: the CLI prints the whole answer; cut it in the shell (for example | head -c 8000)."];
|
|
@@ -134,7 +140,9 @@ async function runLocal(op, call, deps) {
|
|
|
134
140
|
if (error instanceof EngineUnavailableError)
|
|
135
141
|
return engineNotRunning();
|
|
136
142
|
if (error instanceof ToolResultError) {
|
|
137
|
-
|
|
143
|
+
if (def)
|
|
144
|
+
return engineFailure(JSON.stringify(error.result), op, { effect: def.effect, guide });
|
|
145
|
+
return codedLocalFailure(error.result) ?? errorAnswer("invalid_input", error.message, { details: { tool: error.result } });
|
|
138
146
|
}
|
|
139
147
|
return errorAnswer("invalid_input", error instanceof Error ? error.message : String(error), { next: describe });
|
|
140
148
|
}
|
|
@@ -195,12 +203,19 @@ async function waitFor(answer, deps) {
|
|
|
195
203
|
/** A value as one shell word: single quotes, so the shell expands nothing in it. */
|
|
196
204
|
const shellWord = (value) => (/^[\w./:@+=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`);
|
|
197
205
|
/** The answer for a person; `out` is the --out to keep in the printed wait command. */
|
|
206
|
+
/** A failure's problem list (a manifest's fields), one per line: in the answer's details or its tool's structured result. */
|
|
207
|
+
function problemLines(details) {
|
|
208
|
+
const tool = details?.tool && typeof details.tool === "object" ? details.tool : undefined;
|
|
209
|
+
const problems = Array.isArray(details?.problems) ? details.problems : Array.isArray(tool?.problems) ? tool.problems : [];
|
|
210
|
+
const lines = problems.filter((problem) => typeof problem === "string");
|
|
211
|
+
return lines.length ? `\n${lines.map((problem) => ` - ${problem}`).join("\n")}` : "";
|
|
212
|
+
}
|
|
198
213
|
export function formatAnswer(answer, out) {
|
|
199
214
|
const waitFlags = (id) => `summer jobs wait --job-id ${id}${out ? ` --out ${shellWord(out)}` : ""}`;
|
|
200
215
|
if (!answer.ok) {
|
|
201
216
|
const next = answer.error.next;
|
|
202
217
|
const step = !next ? "" : "op" in next ? `\nNext: ${next.op}` : "url" in next ? `\nNext: ${next.label}: ${next.url}` : `\nNext: ${next.say}`;
|
|
203
|
-
return `Error (${answer.error.code}): ${answer.error.message}${step}`;
|
|
218
|
+
return `Error (${answer.error.code}): ${answer.error.message}${problemLines(answer.error.details)}${step}`;
|
|
204
219
|
}
|
|
205
220
|
switch (answer.status) {
|
|
206
221
|
case "done":
|
|
@@ -236,6 +251,8 @@ export async function runOpFromCli(op, argv, deps = defaultOpsCliDeps) {
|
|
|
236
251
|
answer = await waitFor(answer, deps);
|
|
237
252
|
}
|
|
238
253
|
else {
|
|
254
|
+
// Summer's server records a cloud operation itself (X-Summer-Client-Surface: cli).
|
|
255
|
+
noteCliHosted();
|
|
239
256
|
// --out stays here: the files save on this machine once the answer (or the waited job) names them.
|
|
240
257
|
const files = deps.files ?? new FileSaver({ saveByDefault: true });
|
|
241
258
|
const taken = files.take(op, call.input);
|
|
@@ -245,6 +262,8 @@ export async function runOpFromCli(op, argv, deps = defaultOpsCliDeps) {
|
|
|
245
262
|
answer = await waitFor(answer, deps);
|
|
246
263
|
answer = await files.finish(op, answer, out);
|
|
247
264
|
}
|
|
265
|
+
if (!answer.ok)
|
|
266
|
+
noteCliError(answer.error.code);
|
|
248
267
|
(answer.ok ? deps.print : deps.printError)(call.json ? JSON.stringify(answer) : formatAnswer(answer, out));
|
|
249
268
|
if (!call.json && answer.ok)
|
|
250
269
|
for (const warning of answer.warnings ?? [])
|
|
@@ -289,15 +308,89 @@ export async function describeFromCli(op, deps = defaultOpsCliDeps, options = {}
|
|
|
289
308
|
deps.printError(`Warning: ${warning}`);
|
|
290
309
|
return answer.ok ? 0 : 1;
|
|
291
310
|
}
|
|
311
|
+
const HELP_WORDS = new Set(["--help", "-h", "help"]);
|
|
312
|
+
/** gameId -> game-id, look_at -> look-at: the flag a field takes. */
|
|
313
|
+
const flagOf = (field) => field.replace(/_/g, "-").replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
314
|
+
const VIEW_FLAGS = "[--input '<json>'] [--dry-run] [--wait] [--json]";
|
|
315
|
+
/** `summer <area> --help`: the area's actions, from this package and the shipped catalog (no network). */
|
|
316
|
+
export function areaHelp(area) {
|
|
317
|
+
const titles = new Map();
|
|
318
|
+
for (const entry of [...localEntries(), ...shippedCloudEntries()]) {
|
|
319
|
+
if (entry.name.startsWith(`${area}.`) && !titles.has(entry.name))
|
|
320
|
+
titles.set(entry.name, entry.title);
|
|
321
|
+
}
|
|
322
|
+
const actions = [...titles].map(([name, title]) => [flagOf(name.slice(area.length + 1)), title]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
323
|
+
const width = Math.max(0, ...actions.map(([action]) => action.length));
|
|
324
|
+
return [
|
|
325
|
+
`Usage: summer ${area} <action> [--<field> <value> ...] ${VIEW_FLAGS}`,
|
|
326
|
+
"",
|
|
327
|
+
...(actions.length ? ["Actions:", ...actions.map(([action, title]) => ` ${action.padEnd(width)} ${title}`)] : [`This version lists no ${area} operations; signed in, summer ops search ${area} finds the server's.`]),
|
|
328
|
+
"",
|
|
329
|
+
`One action's fields: summer ${area} <action> --help`,
|
|
330
|
+
].join("\n");
|
|
331
|
+
}
|
|
332
|
+
/** One operation's help: its command, what it does, who approves, the sign-in and each field as a flag. */
|
|
333
|
+
export function operationHelp(operation) {
|
|
334
|
+
const [area, action] = operation.name.split(".");
|
|
335
|
+
const command = action ? `summer ${area} ${flagOf(action)}` : `summer ${area}`;
|
|
336
|
+
const required = new Set(operation.input?.required ?? []);
|
|
337
|
+
const fields = Object.entries(operation.input?.properties ?? {}).map(([field, schema]) => {
|
|
338
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
339
|
+
const flag = `--${flagOf(field)}${type === "boolean" ? "" : ` <${type ?? "value"}>`}`;
|
|
340
|
+
const description = schema.description ?? "";
|
|
341
|
+
return [flag, `${required.has(field) ? "Required. " : ""}${description}`.trim()];
|
|
342
|
+
});
|
|
343
|
+
const width = Math.max(0, ...fields.map(([flag]) => flag.length));
|
|
344
|
+
const signIn = operation.auth === "account" ? "Needs a sign-in: summer login (the store sign-in alone: summer login --store)." : "Works signed out.";
|
|
345
|
+
return [
|
|
346
|
+
`Usage: ${command} ${fields.length ? "[--<field> <value> ...] " : ""}${VIEW_FLAGS}`,
|
|
347
|
+
"",
|
|
348
|
+
`${operation.title ?? operation.name}${operation.summary ? `: ${operation.summary}` : ""}`,
|
|
349
|
+
`Effect: ${operation.effect ?? "unknown"}. Approval: ${operation.approval ?? "none"}. ${signIn}`,
|
|
350
|
+
...(fields.length ? ["", "Fields:", ...fields.map(([flag, text]) => ` ${flag.padEnd(width)} ${text}`)] : []),
|
|
351
|
+
"",
|
|
352
|
+
`The exact input: summer ops describe ${operation.name}`,
|
|
353
|
+
].join("\n");
|
|
354
|
+
}
|
|
355
|
+
/** `summer <area> <action> --help`: from this package or the shipped catalog; the server only for an operation newer than both. */
|
|
356
|
+
export async function operationHelpFromCli(op, deps = defaultOpsCliDeps) {
|
|
357
|
+
const name = resolveOpName(op);
|
|
358
|
+
let operation = (localWireForm(name) ?? shippedWireForm(name));
|
|
359
|
+
if (!operation) {
|
|
360
|
+
const asked = await runCloud("ops.describe", { input: { name }, json: true, dryRun: false, wait: false }, deps);
|
|
361
|
+
if (!asked.ok || asked.status !== "done") {
|
|
362
|
+
deps.printError(formatAnswer(asked));
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
operation = asked.result.operation ?? null;
|
|
366
|
+
if (!operation) {
|
|
367
|
+
deps.printError(formatAnswer(errorAnswer("not_found", `No operation is named "${name}".`, { next: { op: "ops.search", input: { query: name } } })));
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
deps.print(operationHelp(operation));
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
292
374
|
function areaCommand(area) {
|
|
293
375
|
return new Command(area)
|
|
294
376
|
.description(`Summer ${area} operations: summer ${area} <action> --field value. Find them: summer ops search ${area}`)
|
|
295
|
-
.argument("
|
|
377
|
+
.argument("[action]", "The operation's action, e.g. list or set-base")
|
|
296
378
|
.allowUnknownOption(true)
|
|
297
379
|
.allowExcessArguments(true)
|
|
298
380
|
.helpOption(false)
|
|
299
381
|
.action(async (action, _options, command) => {
|
|
300
|
-
|
|
382
|
+
// Our own help: commander's would list no actions and no fields.
|
|
383
|
+
if (action === undefined || HELP_WORDS.has(action)) {
|
|
384
|
+
(action === undefined ? defaultOpsCliDeps.printError : defaultOpsCliDeps.print)(areaHelp(area));
|
|
385
|
+
process.exitCode = action === undefined ? 1 : 0;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const argv = command.args.slice(1);
|
|
389
|
+
// A 3.x tool name as the action runs its operation (resolveCliOp).
|
|
390
|
+
const op = resolveCliOp(area, action);
|
|
391
|
+
process.exitCode = argv.some((token) => HELP_WORDS.has(token) && token !== "help")
|
|
392
|
+
? await operationHelpFromCli(op)
|
|
393
|
+
: await runOpFromCli(op, argv);
|
|
301
394
|
});
|
|
302
395
|
}
|
|
303
396
|
export function opsCliCommands() {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summer preview — the running game in a local web page, so an agent's own
|
|
3
|
+
* browser (Claude Code Desktop's Browser pane, the Codex app's in-app browser)
|
|
4
|
+
* can see it, screenshot it and check it after every edit.
|
|
5
|
+
*
|
|
6
|
+
* summer preview http://127.0.0.1:5199 (next free port if taken)
|
|
7
|
+
* summer preview --port 5300 exactly that port (Desktop rewrites this one)
|
|
8
|
+
* summer preview --open also open it in the default browser
|
|
9
|
+
*
|
|
10
|
+
* Run it in the folder the agent session works in: the project itself, a
|
|
11
|
+
* folder inside it, or a folder above it (a repo with the game in game/). The
|
|
12
|
+
* preview shows that project's editor only, and adds a summer-preview entry to
|
|
13
|
+
* <that folder>/.claude/launch.json (never changing other entries), which is
|
|
14
|
+
* where Desktop reads it when the session started there; Desktop then starts
|
|
15
|
+
* the preview itself and verifies changes with it. --no-launch-json skips that.
|
|
16
|
+
* The server, the page and the writer live in core/preview/.
|
|
17
|
+
*/
|
|
18
|
+
import { Command } from "commander";
|
|
19
|
+
import { type EngineSelection } from "../../core/engine.js";
|
|
20
|
+
import type { PreviewEngine } from "../../core/preview/preview-source.js";
|
|
21
|
+
import { type PreviewServer } from "../../core/preview/server.js";
|
|
22
|
+
export interface PreviewCommandOptions {
|
|
23
|
+
port?: string;
|
|
24
|
+
launchJson?: boolean;
|
|
25
|
+
open?: boolean;
|
|
26
|
+
json?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface PreviewIo {
|
|
29
|
+
cwd: string;
|
|
30
|
+
env: NodeJS.ProcessEnv;
|
|
31
|
+
out(line: string): void;
|
|
32
|
+
err(line: string): void;
|
|
33
|
+
connect(selection: EngineSelection | undefined): Promise<PreviewEngine>;
|
|
34
|
+
openUrl(url: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** --port asks for exactly that port; without it, 5199 or the next free one.
|
|
37
|
+
* A PORT variable is not read: shells often export one for a web app, and
|
|
38
|
+
* Desktop passes the port it picked in --port. */
|
|
39
|
+
export declare function resolvePreviewPort(flag: string | undefined): {
|
|
40
|
+
port: number;
|
|
41
|
+
strict: boolean;
|
|
42
|
+
};
|
|
43
|
+
export interface PreviewProject {
|
|
44
|
+
/** The folder with project.godot, or null outside any project. */
|
|
45
|
+
projectRoot: string | null;
|
|
46
|
+
/** Where .claude/launch.json goes: the folder the command runs in. */
|
|
47
|
+
sessionRoot: string;
|
|
48
|
+
/** The entry's cwd when the project sits below the session folder. */
|
|
49
|
+
entryCwd?: string;
|
|
50
|
+
}
|
|
51
|
+
/** The project `summer preview` is for: at or above `cwd` (the same walk as
|
|
52
|
+
* `summer setup`), else the one project below it. Several below is an error. */
|
|
53
|
+
export declare function findPreviewProject(cwd: string): PreviewProject;
|
|
54
|
+
/** Start the preview; resolves with the running server (the command keeps it
|
|
55
|
+
* open until Ctrl-C). Throws on a bad --port, a taken explicit port, or
|
|
56
|
+
* several projects below the folder. */
|
|
57
|
+
export declare function runPreview(opts: PreviewCommandOptions, io: PreviewIo): Promise<PreviewServer>;
|
|
58
|
+
export declare const previewCommand: Command;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summer preview — the running game in a local web page, so an agent's own
|
|
3
|
+
* browser (Claude Code Desktop's Browser pane, the Codex app's in-app browser)
|
|
4
|
+
* can see it, screenshot it and check it after every edit.
|
|
5
|
+
*
|
|
6
|
+
* summer preview http://127.0.0.1:5199 (next free port if taken)
|
|
7
|
+
* summer preview --port 5300 exactly that port (Desktop rewrites this one)
|
|
8
|
+
* summer preview --open also open it in the default browser
|
|
9
|
+
*
|
|
10
|
+
* Run it in the folder the agent session works in: the project itself, a
|
|
11
|
+
* folder inside it, or a folder above it (a repo with the game in game/). The
|
|
12
|
+
* preview shows that project's editor only, and adds a summer-preview entry to
|
|
13
|
+
* <that folder>/.claude/launch.json (never changing other entries), which is
|
|
14
|
+
* where Desktop reads it when the session started there; Desktop then starts
|
|
15
|
+
* the preview itself and verifies changes with it. --no-launch-json skips that.
|
|
16
|
+
* The server, the page and the writer live in core/preview/.
|
|
17
|
+
*/
|
|
18
|
+
import { Command } from "commander";
|
|
19
|
+
import { existsSync, readdirSync, realpathSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join, parse, relative, resolve, sep } from "node:path";
|
|
22
|
+
import open from "open";
|
|
23
|
+
import { EngineApiClient } from "../../core/api-client.js";
|
|
24
|
+
import { engineSelectionFromEnv } from "../../core/engine.js";
|
|
25
|
+
import { c, sym, tildeify } from "../../core/format.js";
|
|
26
|
+
import { DEFAULT_PREVIEW_PORT, previewLaunchEntry, writeClaudeLaunchConfig, } from "../../core/preview/launch-config.js";
|
|
27
|
+
import { PortInUseError, startPreviewServer } from "../../core/preview/server.js";
|
|
28
|
+
import { findSetupProjectRoot } from "../../project-memory/agent-instructions.js";
|
|
29
|
+
/** --port asks for exactly that port; without it, 5199 or the next free one.
|
|
30
|
+
* A PORT variable is not read: shells often export one for a web app, and
|
|
31
|
+
* Desktop passes the port it picked in --port. */
|
|
32
|
+
export function resolvePreviewPort(flag) {
|
|
33
|
+
if (flag === undefined || flag.trim() === "")
|
|
34
|
+
return { port: DEFAULT_PREVIEW_PORT, strict: false };
|
|
35
|
+
const port = Number(flag.trim());
|
|
36
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
37
|
+
throw new Error(`--port must be a whole number from 0 through 65535, got "${flag}".`);
|
|
38
|
+
}
|
|
39
|
+
return { port, strict: true };
|
|
40
|
+
}
|
|
41
|
+
const SEARCH_DEPTH = 3;
|
|
42
|
+
const SEARCH_MAX_DIRS = 2000;
|
|
43
|
+
/** Folders holding project.godot below `dir`: the shallowest level that has
|
|
44
|
+
* any (depth <= 3, no hidden folders or node_modules, at most 2000 read). */
|
|
45
|
+
function projectsBelow(dir) {
|
|
46
|
+
const found = [];
|
|
47
|
+
let read = 0;
|
|
48
|
+
let level = [dir];
|
|
49
|
+
for (let depth = 1; depth <= SEARCH_DEPTH && level.length > 0 && found.length === 0; depth += 1) {
|
|
50
|
+
const next = [];
|
|
51
|
+
for (const parent of level) {
|
|
52
|
+
if (++read > SEARCH_MAX_DIRS)
|
|
53
|
+
return found;
|
|
54
|
+
let names;
|
|
55
|
+
try {
|
|
56
|
+
names = readdirSync(parent, { withFileTypes: true })
|
|
57
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules")
|
|
58
|
+
.map((entry) => entry.name);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
const child = join(parent, name);
|
|
65
|
+
if (existsSync(join(child, "project.godot")))
|
|
66
|
+
found.push(child);
|
|
67
|
+
else
|
|
68
|
+
next.push(child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
level = next;
|
|
72
|
+
}
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
/** The project `summer preview` is for: at or above `cwd` (the same walk as
|
|
76
|
+
* `summer setup`), else the one project below it. Several below is an error. */
|
|
77
|
+
export function findPreviewProject(cwd) {
|
|
78
|
+
const sessionRoot = realpathSync(resolve(cwd));
|
|
79
|
+
const above = findSetupProjectRoot(sessionRoot);
|
|
80
|
+
if (above)
|
|
81
|
+
return { projectRoot: above, sessionRoot };
|
|
82
|
+
if (sessionRoot === realpathSync(homedir()) || sessionRoot === parse(sessionRoot).root) {
|
|
83
|
+
return { projectRoot: null, sessionRoot };
|
|
84
|
+
}
|
|
85
|
+
const below = projectsBelow(sessionRoot);
|
|
86
|
+
if (below.length > 1) {
|
|
87
|
+
throw new Error(`Several Summer projects below ${tildeify(sessionRoot)} (${below.map((dir) => relative(sessionRoot, dir)).join(", ")}). ` +
|
|
88
|
+
"Run summer preview inside the one to show.");
|
|
89
|
+
}
|
|
90
|
+
if (below.length === 0)
|
|
91
|
+
return { projectRoot: null, sessionRoot };
|
|
92
|
+
return { projectRoot: below[0], sessionRoot, entryCwd: relative(sessionRoot, below[0]).split(sep).join("/") };
|
|
93
|
+
}
|
|
94
|
+
function launchLine(result, project, skipped) {
|
|
95
|
+
if (!project.projectRoot) {
|
|
96
|
+
return `${sym.warn()} No Summer project here, above or below (no project.godot): .claude/launch.json not written.`;
|
|
97
|
+
}
|
|
98
|
+
if (skipped)
|
|
99
|
+
return `${c.dim("·")} .claude/launch.json not written (--no-launch-json).`;
|
|
100
|
+
if (!result)
|
|
101
|
+
return `${sym.warn()} .claude/launch.json not written.`;
|
|
102
|
+
const where = tildeify(result.path);
|
|
103
|
+
const reader = `Claude Code Desktop reads it when the session's folder is ${tildeify(project.sessionRoot)}`;
|
|
104
|
+
switch (result.status) {
|
|
105
|
+
case "created":
|
|
106
|
+
case "added":
|
|
107
|
+
return `${sym.ok()} ${result.status === "created" ? "Wrote" : "Added summer-preview to"} ${where}. ${reader}: its Browser pane then starts this preview and checks it after each edit.`;
|
|
108
|
+
case "kept":
|
|
109
|
+
return `${sym.ok()} ${where} already has summer-preview${result.autoVerify ? "" : " (auto-verify is off in that file)"}. ${reader}.`;
|
|
110
|
+
case "skipped":
|
|
111
|
+
return `${sym.warn()} ${where} left as it is: ${result.reason}. Add this entry to its configurations: ${JSON.stringify(result.entry)}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Start the preview; resolves with the running server (the command keeps it
|
|
115
|
+
* open until Ctrl-C). Throws on a bad --port, a taken explicit port, or
|
|
116
|
+
* several projects below the folder. */
|
|
117
|
+
export async function runPreview(opts, io) {
|
|
118
|
+
const { port, strict } = resolvePreviewPort(opts.port);
|
|
119
|
+
const project = findPreviewProject(io.cwd);
|
|
120
|
+
// Only this project's editor: never another project's through the global pointer.
|
|
121
|
+
const selection = engineSelectionFromEnv(io.env) ?? (project.projectRoot ? { projectPath: project.projectRoot } : undefined);
|
|
122
|
+
let server;
|
|
123
|
+
try {
|
|
124
|
+
server = await startPreviewServer({ port, strictPort: strict, connect: () => io.connect(selection), log: io.err });
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (err instanceof PortInUseError) {
|
|
128
|
+
throw new Error(`Port ${err.port} is in use. Pass another --port (0 picks any free one), or stop what runs there.`);
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
let launch = null;
|
|
133
|
+
const skipped = opts.launchJson === false;
|
|
134
|
+
if (project.projectRoot && !skipped) {
|
|
135
|
+
try {
|
|
136
|
+
launch = writeClaudeLaunchConfig(project.sessionRoot, { entry: previewLaunchEntry(DEFAULT_PREVIEW_PORT, project.entryCwd) });
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
io.err(`${sym.warn()} .claude/launch.json not written: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (opts.json) {
|
|
143
|
+
io.out(JSON.stringify({ ok: true, url: server.url, port: server.port, projectRoot: project.projectRoot, launchJson: launch }));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
io.out(`${sym.arrow()} ${c.bold("Summer preview")} ${server.url}`);
|
|
147
|
+
io.out(` Shows the running game, or the editor viewport when no game runs${project.projectRoot ? ` (${tildeify(project.projectRoot)})` : ""}. Ctrl-C to stop.`);
|
|
148
|
+
io.out(` ${launchLine(launch, project, skipped)}`);
|
|
149
|
+
}
|
|
150
|
+
if (opts.open)
|
|
151
|
+
await io.openUrl(server.url).catch(() => undefined);
|
|
152
|
+
return server;
|
|
153
|
+
}
|
|
154
|
+
function defaultIo() {
|
|
155
|
+
return {
|
|
156
|
+
cwd: process.cwd(),
|
|
157
|
+
env: process.env,
|
|
158
|
+
out: (line) => console.log(line),
|
|
159
|
+
err: (line) => console.error(line),
|
|
160
|
+
connect: (selection) => EngineApiClient.connect(selection),
|
|
161
|
+
openUrl: async (url) => {
|
|
162
|
+
await open(url);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export const previewCommand = new Command("preview")
|
|
167
|
+
.description("Show the running game (or the editor viewport) in a local web page an agent's browser can see; registers it in .claude/launch.json")
|
|
168
|
+
.option("--port <n>", `Port to serve on (default ${DEFAULT_PREVIEW_PORT}, or the next free one)`)
|
|
169
|
+
.option("--no-launch-json", "Do not add the summer-preview entry to .claude/launch.json in this folder")
|
|
170
|
+
.option("--open", "Also open the page in the default browser")
|
|
171
|
+
.option("--json", "Print one JSON line with the URL instead of text")
|
|
172
|
+
.action(async (opts) => {
|
|
173
|
+
const server = await runPreview(opts, defaultIo());
|
|
174
|
+
await new Promise((resolve) => {
|
|
175
|
+
const stop = () => resolve();
|
|
176
|
+
process.once("SIGINT", stop);
|
|
177
|
+
process.once("SIGTERM", stop);
|
|
178
|
+
});
|
|
179
|
+
await server.close();
|
|
180
|
+
process.exit(0);
|
|
181
|
+
});
|
|
@@ -1,19 +1,3 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
|
|
3
|
-
export type RunBinaryResolution = {
|
|
4
|
-
binary: string;
|
|
5
|
-
source: "--bin" | typeof ENGINE_BIN_ENV | "installed";
|
|
6
|
-
} | {
|
|
7
|
-
binary: null;
|
|
8
|
-
error: string;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Which engine executable `summer run` launches (and probes with --help):
|
|
12
|
-
* `--bin <path>`, else SUMMER_BIN, else the installed engine
|
|
13
|
-
* (findEngineBinary, which also honours the older SUMMER_ENGINE_BINARY).
|
|
14
|
-
* An explicit override is never silently swapped for the installed engine:
|
|
15
|
-
* a missing path or a bare `.app` bundle is an error, because the caller
|
|
16
|
-
* asked for THAT build.
|
|
17
|
-
*/
|
|
18
|
-
export declare function resolveRunBinary(binFlag: string | undefined, env?: NodeJS.ProcessEnv): RunBinaryResolution;
|
|
2
|
+
export { resolveRunBinary, type RunBinaryResolution } from "../../core/engine-launch.js";
|
|
19
3
|
export declare const runCommand: Command;
|