summer-engine 3.6.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -2
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/CHANGELOG.md +24 -0
  7. package/README.md +11 -5
  8. package/commands/summer.md +2 -1
  9. package/dist/cli/commands/login.d.ts +0 -10
  10. package/dist/cli/commands/login.js +2 -82
  11. package/dist/cli/index.js +0 -4
  12. package/dist/core/auth.d.ts +0 -8
  13. package/dist/core/auth.js +5 -35
  14. package/dist/core/capabilities/apps.d.ts +318 -0
  15. package/dist/core/capabilities/apps.js +340 -0
  16. package/dist/core/capabilities/enable-plugin.d.ts +27 -0
  17. package/dist/core/capabilities/enable-plugin.js +75 -0
  18. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  19. package/dist/core/capabilities/seeing/seeing.d.ts +1 -1
  20. package/dist/core/capabilities/sfx-choices.d.ts +2 -15
  21. package/dist/core/capabilities/sfx-choices.js +26 -64
  22. package/dist/core/capabilities/tool-dispatch.js +51 -54
  23. package/dist/core/capability-skew.js +4 -0
  24. package/dist/core/config.d.ts +1 -7
  25. package/dist/core/config.js +5 -75
  26. package/dist/mcp/hosted-mount.js +13 -1
  27. package/dist/mcp/server.d.ts +2 -1
  28. package/dist/mcp/server.js +8 -5
  29. package/dist/mcp/tools/app-tools.d.ts +2 -0
  30. package/dist/mcp/tools/app-tools.js +64 -0
  31. package/dist/mcp/tools/creator-tools.js +0 -71
  32. package/dist/mcp/tools/generate-tools.js +6 -7
  33. package/gemini-extension.json +3 -1
  34. package/library/references/mcp-tools-reference/mcp-tools-reference.md +24 -5
  35. package/library/skills/README.md +10 -2
  36. package/library/skills/publish-your-game/SKILL.md +2 -0
  37. package/library/skills/sound-effect/SKILL.md +2 -0
  38. package/library/skills/summer-apps/SKILL.md +152 -0
  39. package/library/skills/summer-apps/manifest.md +57 -0
  40. package/library/skills/summer-apps/minimal-app.md +183 -0
  41. package/library/skills/summer-apps/resource.yaml +36 -0
  42. package/library/skills/summer-apps/sdk-reference.md +135 -0
  43. package/library/skills/summer-apps-design/SKILL.md +197 -0
  44. package/library/skills/summer-apps-design/resource.yaml +23 -0
  45. package/library/skills/using-summer/SKILL.md +1 -0
  46. package/library/tools/app-call/resource.yaml +59 -0
  47. package/library/tools/app-create/resource.yaml +67 -0
  48. package/library/tools/app-data/resource.yaml +74 -0
  49. package/library/tools/app-dock/resource.yaml +59 -0
  50. package/library/tools/app-events/resource.yaml +68 -0
  51. package/library/tools/app-get/resource.yaml +46 -0
  52. package/library/tools/app-guide/resource.yaml +55 -0
  53. package/library/tools/app-list/resource.yaml +48 -0
  54. package/library/tools/app-page/resource.yaml +74 -0
  55. package/library/tools/app-publish/resource.yaml +90 -0
  56. package/library/tools/app-report-gap/resource.yaml +58 -0
  57. package/library/tools/app-screenshot/resource.yaml +85 -0
  58. package/library/tools/app-tools/resource.yaml +46 -0
  59. package/library/tools/app-update/resource.yaml +68 -0
  60. package/library/tools/enable-plugin/resource.yaml +53 -0
  61. package/library/tools/generate-sfx-choices/resource.yaml +2 -2
  62. package/package.json +1 -1
  63. package/registry/generated/aliases.json +15 -3
  64. package/registry/generated/counts.json +3 -3
  65. package/registry/generated/gemini-extension.json +3 -1
  66. package/registry/generated/index.json +759 -209
  67. package/registry/generated/marketplace.claude.json +2 -2
  68. package/registry/generated/plugin.claude.json +4 -2
  69. package/registry/generated/plugin.codex.json +5 -3
  70. package/registry/generated/plugin.cursor.json +4 -2
  71. package/registry/generated/plugin.factory.json +4 -2
  72. package/registry/generated/skills-index.md +10 -2
  73. package/registry/generated/skills-registry.json +27 -0
  74. package/registry/schemas/allowed-hosts.json +4 -2
  75. package/dist/cli/commands/publish.d.ts +0 -2
  76. package/dist/cli/commands/publish.js +0 -21
  77. package/dist/cli/commands/releases.d.ts +0 -2
  78. package/dist/cli/commands/releases.js +0 -20
  79. package/dist/core/capabilities/creator.d.ts +0 -65
  80. package/dist/core/capabilities/creator.js +0 -374
  81. package/library/tools/creator-config/resource.yaml +0 -63
  82. package/library/tools/creator-publish/resource.yaml +0 -74
  83. package/library/tools/creator-releases/resource.yaml +0 -53
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * summer_enable_plugin: enable an editor plugin (`res://addons/<name>/`) and
4
+ * report what really happened. Engines with Summer Apps support run the
5
+ * EnableEditorPlugin op until the plugin's `_enter_tree` has run and answer
6
+ * `enabled` (or a failure `code`); older engines only queue the enable and
7
+ * answer `deferred: true`, so this tool says the result is unknown instead of
8
+ * claiming success. One implementation for the MCP tool and `summer tool
9
+ * enable-plugin`.
10
+ */
11
+ export declare const enablePluginArgsSchema: z.ZodObject<{
12
+ plugin: z.ZodString;
13
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ plugin: string;
16
+ timeoutMs?: number | undefined;
17
+ }, {
18
+ plugin: string;
19
+ timeoutMs?: number | undefined;
20
+ }>;
21
+ export declare function enablePluginOp(args: z.output<typeof enablePluginArgsSchema>): {
22
+ op: string;
23
+ [key: string]: unknown;
24
+ };
25
+ export declare const QUEUED_ONLY_NOTE: string;
26
+ /** The engine's answer for EnableEditorPlugin, as one result an agent can trust. */
27
+ export declare function interpretEnablePluginResult(raw: unknown): Record<string, unknown>;
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import { asRecord } from "../util/json.js";
3
+ import { extractOpError } from "./engine-receipt.js";
4
+ /**
5
+ * summer_enable_plugin: enable an editor plugin (`res://addons/<name>/`) and
6
+ * report what really happened. Engines with Summer Apps support run the
7
+ * EnableEditorPlugin op until the plugin's `_enter_tree` has run and answer
8
+ * `enabled` (or a failure `code`); older engines only queue the enable and
9
+ * answer `deferred: true`, so this tool says the result is unknown instead of
10
+ * claiming success. One implementation for the MCP tool and `summer tool
11
+ * enable-plugin`.
12
+ */
13
+ export const enablePluginArgsSchema = z.object({
14
+ plugin: z
15
+ .string()
16
+ .min(1)
17
+ .max(200)
18
+ .describe("The plugin folder under res://addons/ (e.g. 'quest_tools') or its 'res://addons/<name>/plugin.cfg'"),
19
+ timeoutMs: z
20
+ .number()
21
+ .int()
22
+ .min(1000)
23
+ .max(120_000)
24
+ .optional()
25
+ .describe("How long the engine waits for the plugin to load (default 30000)"),
26
+ });
27
+ export function enablePluginOp(args) {
28
+ return { op: "EnableEditorPlugin", pluginPath: args.plugin.trim(), ...(args.timeoutMs ? { timeoutMs: args.timeoutMs } : {}) };
29
+ }
30
+ const FAILURE_HINTS = {
31
+ CFG_MISSING: "No plugin.cfg there. Check the folder name under res://addons/.",
32
+ CFG_PARSE_FAILED: "plugin.cfg does not parse. Fix it and enable again.",
33
+ NO_SCRIPT_KEY: "plugin.cfg needs a script= entry under [plugin].",
34
+ EMPTY_SCRIPT_PATH: "plugin.cfg needs a script= entry under [plugin].",
35
+ SCRIPT_MISSING: "The script plugin.cfg names does not exist.",
36
+ SCRIPT_LOAD_FAILED: "The plugin script did not load. Read summer_get_script_errors for it.",
37
+ SCRIPT_ERROR: "The plugin script has errors. Read summer_get_script_errors, fix them, and enable again.",
38
+ NOT_EDITOR_PLUGIN: "The plugin script must extend EditorPlugin.",
39
+ NOT_TOOL: "The plugin script must start with @tool.",
40
+ TIMEOUT: "The plugin did not finish loading in time (often a filesystem scan). Try again, or raise timeoutMs.",
41
+ };
42
+ export const QUEUED_ONLY_NOTE = "This Summer Engine build only queues the enable and does not report whether the plugin loaded. " +
43
+ "Check summer_get_debugger_errors and summer_get_script_errors for the plugin script before relying on it, or update Summer Engine for the real result.";
44
+ /** The engine's answer for EnableEditorPlugin, as one result an agent can trust. */
45
+ export function interpretEnablePluginResult(raw) {
46
+ const root = asRecord(raw) ?? {};
47
+ const results = Array.isArray(root.results) ? root.results : [];
48
+ const entry = asRecord(results.find((item) => asRecord(item)?.op === "EnableEditorPlugin") ?? results[0]) ?? root;
49
+ const addonPath = typeof entry.addonPath === "string" ? entry.addonPath : undefined;
50
+ const error = extractOpError(raw) ?? (entry.ok === false ? String(entry.error ?? "The plugin was not enabled.") : null);
51
+ if (error) {
52
+ const code = typeof entry.code === "string" ? entry.code : undefined;
53
+ return {
54
+ ok: false,
55
+ enabled: false,
56
+ ...(addonPath ? { addonPath } : {}),
57
+ ...(code ? { code } : {}),
58
+ error,
59
+ ...(code && FAILURE_HINTS[code] ? { hint: FAILURE_HINTS[code] } : {}),
60
+ };
61
+ }
62
+ if (entry.alreadyEnabled === true)
63
+ return { ok: true, enabled: true, alreadyEnabled: true, ...(addonPath ? { addonPath } : {}) };
64
+ if (entry.enabled === true) {
65
+ return {
66
+ ok: true,
67
+ enabled: true,
68
+ ...(addonPath ? { addonPath } : {}),
69
+ autoloadsRegistered: Array.isArray(entry.autoloadsRegistered) ? entry.autoloadsRegistered : [],
70
+ ...(typeof entry.elapsedMs === "number" ? { elapsedMs: entry.elapsedMs } : {}),
71
+ };
72
+ }
73
+ // Older engines: { ok: true, deferred: true } means "scheduled", not "loaded".
74
+ return { ok: true, enabled: null, queued: true, ...(addonPath ? { addonPath } : {}), note: QUEUED_ONLY_NOTE };
75
+ }
@@ -306,7 +306,7 @@ export declare const frameShotArgsSchema: z.ZodObject<{
306
306
  render: z.ZodOptional<z.ZodEnum<["sheet", "best", "none"]>>;
307
307
  max_size: z.ZodOptional<z.ZodNumber>;
308
308
  }, "strict", z.ZodTypeAny, {
309
- shot: "detail" | "establishing" | "eye_level" | "low_angle" | "corridor";
309
+ shot: "establishing" | "eye_level" | "low_angle" | "detail" | "corridor";
310
310
  spawn?: string | undefined;
311
311
  scenePath?: string | undefined;
312
312
  fov?: number | undefined;
@@ -326,7 +326,7 @@ export declare const frameShotArgsSchema: z.ZodObject<{
326
326
  max_soft_fraction?: number | undefined;
327
327
  save_bookmark?: boolean | undefined;
328
328
  }, {
329
- shot: "detail" | "establishing" | "eye_level" | "low_angle" | "corridor";
329
+ shot: "establishing" | "eye_level" | "low_angle" | "detail" | "corridor";
330
330
  spawn?: string | undefined;
331
331
  scenePath?: string | undefined;
332
332
  fov?: number | undefined;
@@ -230,4 +230,4 @@ export interface CorridorScanFloor {
230
230
  }
231
231
  export declare function scanFloorText(floor: CorridorScanFloor, subject: Aabb): string;
232
232
  export declare function frameShot(client: SeeingClient, args: FrameShotArgs): Promise<SeeingResult>;
233
- export declare const SHOT_TYPE_DEFAULTS: Record<"detail" | "establishing" | "eye_level" | "low_angle" | "corridor", import("./candidates.js").ShotDefaults>;
233
+ export declare const SHOT_TYPE_DEFAULTS: Record<"establishing" | "eye_level" | "low_angle" | "detail" | "corridor", import("./candidates.js").ShotDefaults>;
@@ -7,6 +7,8 @@ import { z } from "zod";
7
7
  * POST /api/mcp/generate/audio/choices makes the versions and a choice set,
8
8
  * the person listens and approves one or more on the Studio page
9
9
  * /studio/choices/<id>, and GET /api/mcp/choices/<id> reads the decision.
10
+ * The server queues the page for the person's open Studio tab itself
11
+ * (studioTab.queued); only when no tab took it does this open a browser.
10
12
  * An approval makes that model the person's default for later
11
13
  * summer_generate_audio sound_effects calls without a modelId.
12
14
  *
@@ -58,17 +60,9 @@ export interface GatewayReply {
58
60
  error?: string;
59
61
  status: number;
60
62
  }
61
- export type StudioOpenOutcome = {
62
- ok: true;
63
- } | {
64
- ok: false;
65
- reason: string;
66
- };
67
63
  export interface SfxChoicesDeps {
68
64
  post(endpoint: string, body: Record<string, unknown>, timeoutMs: number): Promise<GatewayReply>;
69
65
  get(endpoint: string, params: URLSearchParams, timeoutMs: number): Promise<GatewayReply>;
70
- /** Move the person's open Studio tab to a /studio path (reviewUrl: the same page as a full URL). */
71
- studioOpen(path: string, reviewUrl: string): Promise<StudioOpenOutcome>;
72
66
  openUrl(url: string): Promise<unknown>;
73
67
  canOpenBrowser(): boolean;
74
68
  }
@@ -77,13 +71,6 @@ export type ShownIn = "studio_tab" | "browser" | "not_opened";
77
71
  export declare function canOpenReviewUrl(url: string): boolean;
78
72
  /** False where a browser launch cannot reach the person: CI, or Linux with no display. */
79
73
  export declare function defaultCanOpenBrowser(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
80
- /**
81
- * Move the person's open Studio tab through the hosted Summer Engine MCP's
82
- * summer_studio_open (needs the store sign-in). Only for the production site:
83
- * the hosted MCP moves tabs on summerengine.com, so a review page on another
84
- * origin (a preview deploy, local web dev) is opened in the browser instead.
85
- */
86
- export declare function openInStudioTab(path: string, reviewUrl: string): Promise<StudioOpenOutcome>;
87
74
  export declare function sfxChoicesRequestBody(args: z.output<typeof sfxChoicesArgsSchema>): Record<string, unknown>;
88
75
  export declare function generateSfxChoices(args: z.output<typeof sfxChoicesArgsSchema>, deps: SfxChoicesDeps): Promise<Record<string, unknown>>;
89
76
  export declare function choicesEndpoint(choiceSetId: string): string;
@@ -1,6 +1,4 @@
1
1
  import { z } from "zod";
2
- import { hasStoreSignIn, hostedResultPayload, withHostedMcp } from "../hosted-mcp.js";
3
- import { resolveMcpResourceUrl } from "../oauth.js";
4
2
  /**
5
3
  * Sound-effect choices (summer_generate_sfx_choices + summer_get_choices).
6
4
  *
@@ -9,6 +7,8 @@ import { resolveMcpResourceUrl } from "../oauth.js";
9
7
  * POST /api/mcp/generate/audio/choices makes the versions and a choice set,
10
8
  * the person listens and approves one or more on the Studio page
11
9
  * /studio/choices/<id>, and GET /api/mcp/choices/<id> reads the decision.
10
+ * The server queues the page for the person's open Studio tab itself
11
+ * (studioTab.queued); only when no tab took it does this open a browser.
12
12
  * An approval makes that model the person's default for later
13
13
  * summer_generate_audio sound_effects calls without a modelId.
14
14
  *
@@ -22,7 +22,6 @@ export const CHOICES_ENDPOINT_PREFIX = "/api/mcp/choices/";
22
22
  /** Three models run in parallel on the server; the slowest decides. */
23
23
  export const SFX_CHOICES_TIMEOUT_MS = 240_000;
24
24
  export const MAX_CHOICES_WAIT_SECONDS = 50;
25
- const STUDIO_OPEN_TIMEOUT_MS = 20_000;
26
25
  export const sfxChoicesArgsSchema = z.object({
27
26
  text: z
28
27
  .string()
@@ -48,7 +47,7 @@ export const sfxChoicesArgsSchema = z.object({
48
47
  open: z
49
48
  .boolean()
50
49
  .default(true)
51
- .describe("Show the choices page to the person (their open Studio tab, else the browser). false: only return the URL"),
50
+ .describe("Show the choices page in the browser when no open Studio tab took it. false: no browser, only return the URL"),
52
51
  });
53
52
  export const getChoicesArgsSchema = z.object({
54
53
  choiceSetId: z.string().min(1).max(200).describe("The choiceSetId from summer_generate_sfx_choices"),
@@ -90,70 +89,28 @@ export function defaultCanOpenBrowser(env = process.env, platform = process.plat
90
89
  return false;
91
90
  return true;
92
91
  }
93
- /**
94
- * Move the person's open Studio tab through the hosted Summer Engine MCP's
95
- * summer_studio_open (needs the store sign-in). Only for the production site:
96
- * the hosted MCP moves tabs on summerengine.com, so a review page on another
97
- * origin (a preview deploy, local web dev) is opened in the browser instead.
98
- */
99
- export async function openInStudioTab(path, reviewUrl) {
100
- if (!isSummerHost(hostOf(reviewUrl)))
101
- return { ok: false, reason: "review_page_not_on_summerengine_com" };
102
- let resource;
103
- try {
104
- resource = resolveMcpResourceUrl();
105
- }
106
- catch {
107
- return { ok: false, reason: "hosted_mcp_url_invalid" };
108
- }
109
- if (!isSummerHost(hostOf(resource)))
110
- return { ok: false, reason: "hosted_mcp_not_summerengine_com" };
111
- if (!(await hasStoreSignIn()))
112
- return { ok: false, reason: "no_store_sign_in" };
113
- let timer;
114
- const timeout = new Promise((resolve) => {
115
- timer = setTimeout(() => resolve({ ok: false, reason: "studio_tab_timeout" }), STUDIO_OPEN_TIMEOUT_MS);
116
- timer.unref?.();
117
- });
118
- const attempt = (async () => {
119
- try {
120
- const result = await withHostedMcp((client) => client.callTool({ name: "summer_studio_open", arguments: { path } }));
121
- const payload = hostedResultPayload(result);
122
- if (payload.isError || payload.json?.ok === false) {
123
- const code = typeof payload.json?.error === "string" ? payload.json.error : "studio_open_failed";
124
- return { ok: false, reason: code };
125
- }
126
- return { ok: true };
127
- }
128
- catch (error) {
129
- return { ok: false, reason: error instanceof Error ? error.message : String(error) };
130
- }
131
- })();
132
- try {
133
- return await Promise.race([attempt, timeout]);
134
- }
135
- finally {
136
- clearTimeout(timer);
137
- }
92
+ /** The server's studioTab answer: queued for the person's open tab, or why not. Missing on older servers. */
93
+ function studioTabAnswer(value) {
94
+ if (!value || typeof value !== "object")
95
+ return null;
96
+ const record = value;
97
+ return {
98
+ queued: record.queued === true,
99
+ ...(typeof record.reason === "string" ? { reason: record.reason } : {}),
100
+ };
138
101
  }
139
- async function showChoicesPage(reviewUrl, studioPath, deps) {
140
- let studioTab;
141
- if (studioPath) {
142
- const moved = await deps.studioOpen(studioPath, reviewUrl);
143
- if (moved.ok)
144
- return { shown: "studio_tab" };
145
- studioTab = moved.reason;
146
- }
102
+ async function showChoicesPage(reviewUrl, studioTab, deps) {
103
+ const notQueued = studioTab ? (studioTab.reason ?? "not_queued") : undefined;
147
104
  if (!canOpenReviewUrl(reviewUrl))
148
- return { shown: "not_opened", studioTab, browser: "not_a_summer_url" };
105
+ return { shown: "not_opened", studioTab: notQueued, browser: "not_a_summer_url" };
149
106
  if (!deps.canOpenBrowser())
150
- return { shown: "not_opened", studioTab, browser: "no_browser_here" };
107
+ return { shown: "not_opened", studioTab: notQueued, browser: "no_browser_here" };
151
108
  try {
152
109
  await deps.openUrl(reviewUrl);
153
- return { shown: "browser", studioTab };
110
+ return { shown: "browser", studioTab: notQueued };
154
111
  }
155
112
  catch (error) {
156
- return { shown: "not_opened", studioTab, browser: error instanceof Error ? error.message : String(error) };
113
+ return { shown: "not_opened", studioTab: notQueued, browser: error instanceof Error ? error.message : String(error) };
157
114
  }
158
115
  }
159
116
  // ---------------------------------------------------------------------------
@@ -198,9 +155,14 @@ export async function generateSfxChoices(args, deps) {
198
155
  hint: "Read failed[] for each model's error. Fix the request (a longer durationSeconds, a plainer description) or call summer_generate_audio with one modelId.",
199
156
  };
200
157
  }
201
- const shown = args.open
202
- ? await showChoicesPage(reviewUrl, studioPath, deps)
203
- : { shown: "not_opened", browser: "open_false" };
158
+ // The server queued the Studio-tab open already; that happens even with
159
+ // open:false, so report it rather than hide it.
160
+ const studioTab = studioTabAnswer(data.studioTab);
161
+ const shown = studioTab?.queued
162
+ ? { shown: "studio_tab" }
163
+ : args.open
164
+ ? await showChoicesPage(reviewUrl, studioTab, deps)
165
+ : { shown: "not_opened", browser: "open_false" };
204
166
  const count = clips.length;
205
167
  const where = shown.shown === "studio_tab"
206
168
  ? "I opened them in your Summer Studio tab"
@@ -30,7 +30,7 @@ import { lookupApiDocs } from "./api-docs.js";
30
30
  import { BOARD_ENDPOINT, boardArgsSchema, boardQuery } from "./board.js";
31
31
  import { IMAGE_GENERATION_TIMEOUT_MS, imageGenerationArgsSchema, saveGeneratedImage } from "./image-generation.js";
32
32
  import { buildMotionRequestBody, motionGenerationArgsSchema } from "./motion-generation.js";
33
- import { defaultCanOpenBrowser, generateSfxChoices, getChoices, getChoicesArgsSchema, openInStudioTab, sfxChoicesArgsSchema, } from "./sfx-choices.js";
33
+ import { defaultCanOpenBrowser, generateSfxChoices, getChoices, getChoicesArgsSchema, sfxChoicesArgsSchema, } from "./sfx-choices.js";
34
34
  import { ImportHdriError, importHdriArgsSchema, importPolyHavenHdri } from "./hdri-import.js";
35
35
  import { FABRICATE_FALLBACK, buildFabricateMeshOp, fabricateArgsSchema } from "./fabricate-mesh.js";
36
36
  import { buildUiActionsOp, buildUiActivateOp, buildUiScreenshotOp, buildUiTreeOp, executeUiOp, uiActionsArgsSchema, uiActivateArgsSchema, uiScreenshotArgsSchema, uiScreenshotCaption, uiScreenshotImage, uiTreeArgsSchema, withUiFailureDetails, } from "./ui-control.js";
@@ -47,12 +47,11 @@ import { getAuthToken } from "../auth.js";
47
47
  import open from "open";
48
48
  import { openArgsSchema, runOpen } from "./navigation/open.js";
49
49
  import { shapeEngineLogResponse } from "../log-filters.js";
50
- import { listCreatorReleases, publishCreator, } from "./creator.js";
51
50
  import { exportGame } from "./export-game.js";
52
51
  import { exportTemplates } from "./export-templates.js";
53
52
  import { captureGameplay } from "./capture-gameplay.js";
54
53
  import { publishBuild } from "./publish-build.js";
55
- import { CONFIG_KEYS, getConfigValue, isConfigKey, readSummerConfig, resolveGatewayUrl, setConfigValue, unsetConfigValue, } from "../config.js";
54
+ import { resolveGatewayUrl } from "../config.js";
56
55
  import { createDebugReportArtifact } from "./debug-report.js";
57
56
  import { buildGameTaskPlan, gameTaskPlanInputSchema } from "./game-task-plan.js";
58
57
  import { buildProjectContext, projectContextInputSchema, projectSettingValue, } from "./project-context.js";
@@ -77,6 +76,8 @@ import { attachToSurface, attachToSurfaceArgsSchema, connectPorts, connectPortsA
77
76
  import { readLibraryEntry, readLibraryInputSchema } from "../library-read.js";
78
77
  import { runSearchLibrary, searchLibraryInputSchema } from "../library-search.js";
79
78
  import { TOOLKIT_VERSION as CLI_VERSION } from "../version.js";
79
+ import { APP_DOCK_FALLBACK, appCall, appCallArgsSchema, appCreate, appCreateArgsSchema, appData, appDataArgsSchema, appDockArgsSchema, appDockOp, appEvents, appEventsArgsSchema, appGet, appGetArgsSchema, appGuide, appGuideArgsSchema, appList, appListArgsSchema, appPage, appPageArgsSchema, appScreenshot, appScreenshotArgsSchema, appUpdate, appUpdateArgsSchema, appPublish, appPublishArgsSchema, appReportGap, appReportGapArgsSchema, appsGateway, appTools, appToolsArgsSchema, } from "./apps.js";
80
+ import { enablePluginArgsSchema, enablePluginOp, interpretEnablePluginResult } from "./enable-plugin.js";
80
81
  /** Dispatch-level failure with a user-facing message (no stack needed). */
81
82
  export class ToolDispatchError extends Error {
82
83
  }
@@ -405,11 +406,18 @@ async function gatewayReply(call) {
405
406
  return { error: error.message, status: 0 };
406
407
  }
407
408
  }
409
+ /** summer tool app-screenshot: the PNG goes to `out` (default ./summer-app-screenshot.png), not into the printed JSON. */
410
+ async function cliScreenshot(args, deps) {
411
+ const shot = await appScreenshot({ ...args, out: args.out ?? "summer-app-screenshot.png" }, deps);
412
+ if (shot.ok !== true || !("image" in shot))
413
+ return shot;
414
+ const { image: _image, ...report } = shot;
415
+ return report;
416
+ }
408
417
  function sfxChoicesDeps() {
409
418
  return {
410
419
  post: (endpoint, body, timeoutMs) => gatewayReply(() => gatewayPost(endpoint, body, timeoutMs)),
411
420
  get: (endpoint, params, timeoutMs) => gatewayReply(() => gatewayGet(endpoint, params, timeoutMs)),
412
- studioOpen: (path, reviewUrl) => openInStudioTab(path, reviewUrl),
413
421
  openUrl: (url) => open(url),
414
422
  canOpenBrowser: () => defaultCanOpenBrowser(),
415
423
  };
@@ -674,56 +682,6 @@ export const TOOL_DISPATCH = [
674
682
  ...(typeof args.waitSeconds === "number" ? { waitSeconds: args.waitSeconds } : {}),
675
683
  face: "cli",
676
684
  })),
677
- entry("summer_creator_publish", "Deprecated: publish a .pck to the legacy creator API; use publish-build", false, (args) => publishCreator({
678
- project: optStr(args, "project"),
679
- artifact: optStr(args, "artifact"),
680
- version: optStr(args, "version"),
681
- manifest: optStr(args, "manifest"),
682
- projectId: optStr(args, "projectId"),
683
- channel: optStr(args, "channel"),
684
- notes: optStr(args, "notes"),
685
- confirm: args.confirm === true,
686
- face: "cli",
687
- })),
688
- entry("summer_creator_releases", "Deprecated: list releases on the legacy creator API; use summer_store_releases", false, (args) => listCreatorReleases({
689
- projectId: optStr(args, "projectId"),
690
- limit: typeof args.limit === "number" ? args.limit : 20,
691
- cursor: optStr(args, "cursor"),
692
- face: "cli",
693
- })),
694
- entry("summer_creator_config", "Read or update the shared non-secret Summer configuration", false, async (args) => {
695
- const action = str(args, "action");
696
- if (action === "list") {
697
- const config = await readSummerConfig();
698
- return {
699
- ok: true,
700
- values: Object.fromEntries(CONFIG_KEYS.map((name) => [name, getConfigValue(config, name) ?? null])),
701
- };
702
- }
703
- const key = optStr(args, "key");
704
- if (!key || !isConfigKey(key)) {
705
- throw new ToolDispatchError(`A valid key is required. Use one of ${CONFIG_KEYS.join(", ")}.`);
706
- }
707
- if (action === "get") {
708
- return { ok: true, key, value: getConfigValue(await readSummerConfig(), key) ?? null };
709
- }
710
- if (args.confirm !== true) {
711
- throw new ToolDispatchError(`Changing ${key} requires confirmation. Retry with "confirm": true after the user approves the exact change.`);
712
- }
713
- if (action === "set") {
714
- const value = optStr(args, "value");
715
- if (value === undefined) {
716
- throw new ToolDispatchError(`A value is required for ${key}.`);
717
- }
718
- const config = await setConfigValue(key, value);
719
- return { ok: true, key, value: getConfigValue(config, key) };
720
- }
721
- if (action === "unset") {
722
- const config = await unsetConfigValue(key);
723
- return { ok: true, key, value: getConfigValue(config, key) ?? null };
724
- }
725
- throw new ToolDispatchError(`Unknown action "${action}". Use list, get, set, or unset.`);
726
- }),
727
685
  // --- debug ---
728
686
  entry("summer_get_diagnostics", "Overview of editor console and runtime debugger errors/warnings", true, async (args, ctx) => requireEngineSuccess(await (await ctx.engine()).getDiagnostics())),
729
687
  entry("summer_get_console", "Read recent editor Output panel messages (deduplicated)", true, async (args, ctx) => {
@@ -958,6 +916,45 @@ export const TOOL_DISPATCH = [
958
916
  throw new ToolResultError(result, String(result.error));
959
917
  return result;
960
918
  }),
919
+ // Summer Apps (core/capabilities/apps.ts): the same functions as the MCP face.
920
+ ...[
921
+ ["summer_app_guide", "Read the Summer Apps build guide (SDK, manifest, kit, layout, design, examples)", appGuideArgsSchema, appGuide],
922
+ ["summer_app_list", "List the Summer Apps in the person's workspaces", appListArgsSchema, appList],
923
+ ["summer_app_create", "Create a scratch Summer App and open it in Summer Studio", appCreateArgsSchema, appCreate],
924
+ ["summer_app_publish", "Publish a new version of a Summer App from a folder or files", appPublishArgsSchema, appPublish],
925
+ ["summer_app_get", "Read a Summer App's manifest, files and data summary", appGetArgsSchema, appGet],
926
+ ["summer_app_update", "Rename a Summer App or keep, archive or restore it", appUpdateArgsSchema, appUpdate],
927
+ ["summer_app_events", "Wait for events a Summer App sends to agents", appEventsArgsSchema, appEvents],
928
+ ["summer_app_page", "Read or use the open app's declared fields and buttons", appPageArgsSchema, appPage],
929
+ ["summer_app_data", "Read or write a Summer App's shared data", appDataArgsSchema, appData],
930
+ ["summer_app_screenshot", "See a Summer App rendered (read-only) with its console and errors; out saves the PNG", appScreenshotArgsSchema, cliScreenshot],
931
+ ["summer_app_tools", "List a Summer App's agent tools", appToolsArgsSchema, appTools],
932
+ ["summer_app_call", "Run a Summer App's agent tool in the person's open app", appCallArgsSchema, appCall],
933
+ ["summer_app_report_gap", "Report an SDK capability a Summer App needed", appReportGapArgsSchema, appReportGap],
934
+ ].map(([name, summary, schema, run]) => entry(name, summary, false, async (args) => {
935
+ const result = await run(parseToolArgs(schema, args, name.replace(/^summer_/, "").replace(/_/g, "-")), { request: appsGateway("cli", name) });
936
+ if (result.ok !== true)
937
+ throw new ToolResultError(result, String(result.error));
938
+ return result;
939
+ })),
940
+ entry("summer_enable_plugin", "Enable an editor plugin and report whether it really loaded", true, async (args, ctx) => {
941
+ const parsed = parseToolArgs(enablePluginArgsSchema, args, "enable-plugin");
942
+ const result = interpretEnablePluginResult(await (await ctx.engine()).executeOps([enablePluginOp(parsed)]));
943
+ if (result.ok !== true)
944
+ throw new ToolResultError(result, String(result.error));
945
+ return result;
946
+ }),
947
+ entry("summer_app_dock", "Dock a Summer App in the editor, close its dock, or list docks", true, async (args, ctx) => {
948
+ const parsed = parseToolArgs(appDockArgsSchema, args, "app-dock");
949
+ if (parsed.action !== "list" && !parsed.appId)
950
+ throw new ToolDispatchError(`${parsed.action} needs appId`);
951
+ const op = appDockOp(parsed);
952
+ const client = await ctx.engine();
953
+ const missing = missingEngineOpResult(client, op.op, APP_DOCK_FALLBACK);
954
+ if (missing)
955
+ throw new ToolResultError({ ...missing }, missing.error);
956
+ return requireEngineSuccess(await client.executeOps([op]));
957
+ }),
961
958
  entry("summer_generate_3d", "Generate a 3D model (optional auto-rig + animations)", false, async (args) => {
962
959
  const options = {
963
960
  ...(args.options ?? {}),
@@ -49,6 +49,10 @@ export const CLI_KNOWN_OP_NEEDS = [
49
49
  // engine's one-table op; the other three are the legacy
50
50
  // fallbacks on engines that predate it.
51
51
  "Navigate", "OpenResource", "FocusDock", "RevealInFileSystem",
52
+ // Summer Apps editor docks (summer_app_dock: core/capabilities/apps.ts)
53
+ "OpenAppDock", "CloseAppDock", "ListAppDocks",
54
+ // summer_enable_plugin (core/capabilities/enable-plugin.ts)
55
+ "EnableEditorPlugin",
52
56
  // Project + input
53
57
  "ProjectSetting", "InputMapAddAction", "InputMapBind",
54
58
  // Files (summer_write_file / summer_replace_text / summer_create_scene)
@@ -3,13 +3,8 @@ export interface SummerConfig {
3
3
  gateway?: {
4
4
  url?: string;
5
5
  };
6
- creator?: {
7
- apiUrl?: string;
8
- projectId?: string;
9
- channel?: string;
10
- };
11
6
  }
12
- export declare const CONFIG_KEYS: readonly ["gateway.url", "creator.apiUrl", "creator.projectId", "creator.channel"];
7
+ export declare const CONFIG_KEYS: readonly ["gateway.url"];
13
8
  export type ConfigKey = (typeof CONFIG_KEYS)[number];
14
9
  export declare function readSummerConfig(): Promise<SummerConfig>;
15
10
  export declare function getConfigValue(config: SummerConfig, key: ConfigKey): string | undefined;
@@ -27,4 +22,3 @@ export declare function isConfigKey(value: string): value is ConfigKey;
27
22
  * Returns an origin-like base without a trailing slash.
28
23
  */
29
24
  export declare function resolveGatewayUrl(): Promise<string>;
30
- export declare function getCreatorApiUrl(): Promise<string>;
@@ -1,13 +1,7 @@
1
1
  import { readStoreJson, writeStoreJson } from "./store.js";
2
2
  const CONFIG_FILE = "config.json";
3
3
  const DEFAULT_GATEWAY_URL = "https://www.summerengine.com";
4
- const DEFAULT_CREATOR_API_URL = "https://summercraft.ai";
5
- export const CONFIG_KEYS = [
6
- "gateway.url",
7
- "creator.apiUrl",
8
- "creator.projectId",
9
- "creator.channel",
10
- ];
4
+ export const CONFIG_KEYS = ["gateway.url"];
11
5
  function emptyConfig() {
12
6
  return { schemaVersion: 1 };
13
7
  }
@@ -39,90 +33,30 @@ function validateGatewayUrl(value) {
39
33
  }
40
34
  return parsed.toString().replace(/\/+$/, "");
41
35
  }
42
- function validateCreatorApiUrl(value) {
43
- let parsed;
44
- try {
45
- parsed = new URL(value);
46
- }
47
- catch {
48
- throw new Error('creator.apiUrl must be a complete URL. Recovery: use "summer config set creator.apiUrl https://summercraft.ai".');
49
- }
50
- const local = parsed.hostname === "localhost" ||
51
- parsed.hostname === "127.0.0.1" ||
52
- parsed.hostname === "[::1]";
53
- if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:")) {
54
- throw new Error("creator.apiUrl must use HTTPS (HTTP is allowed only for localhost). Recovery: set an HTTPS URL and retry.");
55
- }
56
- if (parsed.username ||
57
- parsed.password ||
58
- parsed.pathname !== "/" ||
59
- parsed.search ||
60
- parsed.hash) {
61
- throw new Error("creator.apiUrl must be an origin without credentials, a path, query parameters, or fragments.");
62
- }
63
- return parsed.origin;
64
- }
65
36
  function validateValue(key, value) {
66
37
  const clean = value.trim();
67
38
  if (!clean) {
68
39
  throw new Error(`Cannot set ${key} to an empty value. Recovery: provide a value, or run "summer config unset ${key}".`);
69
40
  }
70
- if (key === "gateway.url")
71
- return validateGatewayUrl(clean);
72
- if (key === "creator.apiUrl")
73
- return validateCreatorApiUrl(clean);
74
- if (key === "creator.channel" && !/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(clean)) {
75
- throw new Error("creator.channel may contain letters, numbers, dots, underscores, and hyphens. Recovery: choose a channel such as production or preview.");
76
- }
77
- if (key === "creator.projectId" && clean.length > 128) {
78
- throw new Error("creator.projectId is too long. Recovery: copy the project ID from the Summer creator dashboard and retry.");
79
- }
80
- return clean;
41
+ return validateGatewayUrl(clean);
81
42
  }
82
43
  export function getConfigValue(config, key) {
83
- if (key === "gateway.url")
84
- return config.gateway?.url;
85
- if (key === "creator.apiUrl")
86
- return config.creator?.apiUrl;
87
- if (key === "creator.projectId")
88
- return config.creator?.projectId;
89
- return config.creator?.channel;
44
+ return config.gateway?.url;
90
45
  }
91
46
  export async function setConfigValue(key, rawValue) {
92
47
  const config = await readSummerConfig();
93
48
  const value = validateValue(key, rawValue);
94
- if (key === "gateway.url") {
95
- config.gateway = { ...config.gateway, url: value };
96
- }
97
- else if (key === "creator.apiUrl") {
98
- config.creator = { ...config.creator, apiUrl: value };
99
- }
100
- else if (key === "creator.projectId") {
101
- config.creator = { ...config.creator, projectId: value };
102
- }
103
- else {
104
- config.creator = { ...config.creator, channel: value };
105
- }
49
+ config.gateway = { ...config.gateway, url: value };
106
50
  await writeStoreJson(CONFIG_FILE, config);
107
51
  return config;
108
52
  }
109
53
  export async function unsetConfigValue(key) {
110
54
  const config = await readSummerConfig();
111
- if (key === "gateway.url" && config.gateway)
55
+ if (config.gateway)
112
56
  delete config.gateway.url;
113
- if (key === "creator.apiUrl" && config.creator)
114
- delete config.creator.apiUrl;
115
- if (key === "creator.projectId" && config.creator) {
116
- delete config.creator.projectId;
117
- }
118
- if (key === "creator.channel" && config.creator)
119
- delete config.creator.channel;
120
57
  if (config.gateway && Object.keys(config.gateway).length === 0) {
121
58
  delete config.gateway;
122
59
  }
123
- if (config.creator && Object.keys(config.creator).length === 0) {
124
- delete config.creator;
125
- }
126
60
  await writeStoreJson(CONFIG_FILE, config);
127
61
  return config;
128
62
  }
@@ -147,7 +81,3 @@ export async function resolveGatewayUrl() {
147
81
  const configured = config.gateway?.url?.trim();
148
82
  return configured ? validateGatewayUrl(configured) : DEFAULT_GATEWAY_URL;
149
83
  }
150
- export async function getCreatorApiUrl() {
151
- const config = await readSummerConfig();
152
- return config.creator?.apiUrl ?? DEFAULT_CREATOR_API_URL;
153
- }
@@ -1,4 +1,4 @@
1
- import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
1
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { z } from "zod";
3
3
  import { connectHostedMcp } from "../core/hosted-mcp.js";
4
4
  import { getStoreAccessToken, OAuthError, resolveMcpResourceUrl } from "../core/oauth.js";
@@ -79,6 +79,18 @@ export function prepareHostedMount(server, deps = defaultHostedMountDependencies
79
79
  };
80
80
  });
81
81
  }
82
+ // tools/call for a name the server does not know yet: a hosted tool whose mount
83
+ // is still running (an agent that calls before it lists). Wait for that mount,
84
+ // at most the first-list cap, then dispatch; an unknown name still errors.
85
+ const callTool = handlers.get("tools/call");
86
+ if (callTool) {
87
+ server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
88
+ const name = request.params?.name;
89
+ if (typeof name === "string" && !(name in registries._registeredTools))
90
+ await waitForMount(firstListWaitMs);
91
+ return (await callTool(request, extra));
92
+ });
93
+ }
82
94
  let statusTool = null;
83
95
  let running = null;
84
96
  /** Wait for a mount in progress, at most ms; returns at once when none runs. */
@@ -11,7 +11,8 @@ export declare const getCachedBootDriftNotice: typeof getCachedNotice;
11
11
  * turn and would otherwise learn the hard way. Kept under 600 characters so
12
12
  * it costs a few hundred tokens once; the full operating guide stays in the
13
13
  * summer_agent_playbook prompt / summer_get_agent_playbook tool. The last
14
- * sentence names the publishing path and the owner-approval rule.
14
+ * sentence names the publishing path, the owner-approval rule and where
15
+ * review feedback is read.
15
16
  */
16
17
  export declare const SUMMER_MCP_INSTRUCTIONS: string;
17
18
  export declare function configureMcpEngineSelection(selection?: EngineSelection): void;