summer-engine 4.6.0 → 4.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 (101) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +5 -4
  4. package/.cursor-plugin/plugin.json +4 -3
  5. package/.factory-plugin/plugin.json +4 -3
  6. package/CHANGELOG.md +37 -0
  7. package/README.md +3 -3
  8. package/commands/start.md +1 -1
  9. package/dist/cli/commands/api.d.ts +43 -0
  10. package/dist/cli/commands/api.js +191 -0
  11. package/dist/cli/commands/login.d.ts +2 -5
  12. package/dist/cli/commands/login.js +10 -81
  13. package/dist/cli/commands/token.d.ts +17 -0
  14. package/dist/cli/commands/token.js +25 -0
  15. package/dist/cli/index.js +4 -0
  16. package/dist/core/api-client.d.ts +5 -1
  17. package/dist/core/api-client.js +21 -0
  18. package/dist/core/capabilities/doctor.d.ts +1 -1
  19. package/dist/core/capabilities/doctor.js +1 -1
  20. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  21. package/dist/core/capabilities/navigation/open.d.ts +3 -0
  22. package/dist/core/capabilities/navigation/open.js +53 -0
  23. package/dist/core/capabilities/projects-local.js +2 -1
  24. package/dist/core/capabilities/runtime-control.d.ts +2 -2
  25. package/dist/core/capabilities/seeing/args.d.ts +4 -4
  26. package/dist/core/capabilities/tool-dispatch.js +37 -0
  27. package/dist/core/capabilities/ui-control.d.ts +2 -2
  28. package/dist/core/capabilities/view-set.d.ts +11 -0
  29. package/dist/core/capabilities/view-set.js +15 -0
  30. package/dist/core/capability-skew.d.ts +8 -0
  31. package/dist/core/capability-skew.js +18 -0
  32. package/dist/core/cli-session.d.ts +25 -0
  33. package/dist/core/cli-session.js +97 -0
  34. package/dist/core/engine-self-start.d.ts +20 -0
  35. package/dist/core/engine-self-start.js +108 -5
  36. package/dist/core/oauth.d.ts +15 -2
  37. package/dist/core/oauth.js +26 -3
  38. package/dist/core/ops/areas.d.ts +1 -1
  39. package/dist/core/ops/areas.js +5 -3
  40. package/dist/core/ops/engine-answer.js +5 -2
  41. package/dist/core/ops/engine.js +6 -0
  42. package/dist/core/ops/save-files.d.ts +2 -0
  43. package/dist/core/ops/save-files.js +13 -0
  44. package/dist/core/ops/studio-page.d.ts +22 -0
  45. package/dist/core/ops/studio-page.js +63 -0
  46. package/dist/core/sign-in-link.d.ts +38 -0
  47. package/dist/core/sign-in-link.js +106 -0
  48. package/dist/core/store.js +19 -1
  49. package/dist/core/summer-api.d.ts +114 -0
  50. package/dist/core/summer-api.js +281 -0
  51. package/dist/mcp/ops-surface.d.ts +3 -0
  52. package/dist/mcp/ops-surface.js +6 -0
  53. package/dist/mcp/server.js +16 -4
  54. package/dist/mcp/tool-results.d.ts +9 -0
  55. package/dist/mcp/tool-results.js +64 -0
  56. package/dist/mcp/tool-surface.js +7 -1
  57. package/dist/mcp/tools/api-tools.d.ts +50 -0
  58. package/dist/mcp/tools/api-tools.js +65 -0
  59. package/dist/mcp/tools/local-project-tools.d.ts +14 -0
  60. package/dist/mcp/tools/local-project-tools.js +13 -2
  61. package/dist/mcp/tools/navigation-tools.js +1 -0
  62. package/dist/mcp/tools/scene-tools.js +7 -0
  63. package/gemini-extension.json +3 -2
  64. package/library/references/mcp-tools-reference/mcp-tools-reference.md +15 -2
  65. package/library/references/mcp-tools-reference/resource.yaml +1 -1
  66. package/library/skills/README.md +6 -5
  67. package/library/skills/design-npc/SKILL.md +1 -1
  68. package/library/skills/design-npc/resource.yaml +2 -1
  69. package/library/skills/navigate-summer/SKILL.md +23 -2
  70. package/library/skills/navigate-summer/resource.yaml +5 -3
  71. package/library/skills/summer-api/SKILL.md +198 -0
  72. package/library/skills/summer-api/resource.yaml +38 -0
  73. package/library/skills/summer-assets/SKILL.md +1 -1
  74. package/library/skills/summer-assets/resource.yaml +3 -2
  75. package/library/skills/summer-router/SKILL.md +251 -0
  76. package/library/skills/summer-router/resource.yaml +35 -0
  77. package/library/tools/api-batch/resource.yaml +82 -0
  78. package/library/tools/api-estimate/resource.yaml +52 -0
  79. package/library/tools/api-job/resource.yaml +58 -0
  80. package/library/tools/api-models/resource.yaml +57 -0
  81. package/library/tools/api-run/resource.yaml +70 -0
  82. package/library/tools/view-set/GUIDE.md +3 -0
  83. package/library/tools/view-set/resource.yaml +78 -0
  84. package/package.json +3 -3
  85. package/registry/generated/aliases.json +7 -0
  86. package/registry/generated/counts.json +3 -3
  87. package/registry/generated/gemini-extension.json +3 -2
  88. package/registry/generated/index.json +316 -51
  89. package/registry/generated/marketplace.claude.json +1 -1
  90. package/registry/generated/plugin.claude-directory.json +4 -3
  91. package/registry/generated/plugin.claude.json +3 -2
  92. package/registry/generated/plugin.codex.json +5 -4
  93. package/registry/generated/plugin.cursor.json +4 -3
  94. package/registry/generated/plugin.factory.json +4 -3
  95. package/registry/generated/server.json +2 -2
  96. package/registry/generated/skills-index.md +6 -5
  97. package/registry/generated/skills-registry.json +29 -15
  98. package/registry/operations/legacy-names.json +30 -0
  99. package/registry/operations/public-catalog.json +9169 -4926
  100. package/library/skills/ai-characters-in-games/SKILL.md +0 -235
  101. package/library/skills/ai-characters-in-games/resource.yaml +0 -30
@@ -0,0 +1,17 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * `summer token`: print the Summer sign-in's current access token (Supabase
4
+ * OAuth, about one hour; refreshed first when it is about to expire), for
5
+ * curl against the Summer API:
6
+ *
7
+ * curl -H "Authorization: Bearer $(summer token)" https://www.summerengine.com/v1/models
8
+ *
9
+ * Only the token goes to stdout; a missing sign-in says so on stderr and exits 1.
10
+ */
11
+ export interface TokenCommandDependencies {
12
+ token: () => Promise<string>;
13
+ print: (line: string) => void;
14
+ printError: (line: string) => void;
15
+ }
16
+ export declare function runTokenCommand(overrides?: Partial<TokenCommandDependencies>): Promise<number>;
17
+ export declare const tokenCommand: Command;
@@ -0,0 +1,25 @@
1
+ import { Command } from "commander";
2
+ import { getStoreAccessToken, OAuthError } from "../../core/oauth.js";
3
+ const defaults = () => ({
4
+ token: () => getStoreAccessToken(),
5
+ print: (line) => process.stdout.write(`${line}\n`),
6
+ printError: (line) => console.error(line),
7
+ });
8
+ export async function runTokenCommand(overrides = {}) {
9
+ const deps = { ...defaults(), ...overrides };
10
+ try {
11
+ deps.print(await deps.token());
12
+ return 0;
13
+ }
14
+ catch (error) {
15
+ deps.printError(error instanceof OAuthError ? 'This computer is not signed in to Summer. Run "summer login", then retry.' : error instanceof Error ? error.message : String(error));
16
+ return 1;
17
+ }
18
+ }
19
+ export const tokenCommand = new Command("token")
20
+ .description("Print a one-hour Summer API access token from this computer's Summer sign-in (summer login), for curl: Authorization: Bearer $(summer token)")
21
+ .action(async () => {
22
+ const code = await runTokenCommand();
23
+ if (code)
24
+ process.exitCode = code;
25
+ });
package/dist/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { loginCommand } from "./commands/login.js";
3
3
  import { logoutCommand } from "./commands/logout.js";
4
+ import { tokenCommand } from "./commands/token.js";
4
5
  import { statusCommand } from "./commands/status.js";
5
6
  import { runCommand } from "./commands/run.js";
6
7
  import { openCommand } from "./commands/open.js";
@@ -18,6 +19,7 @@ import { configCommand } from "./commands/config.js";
18
19
  import { eventsCommand } from "./commands/events.js";
19
20
  import { previewCommand } from "./commands/preview.js";
20
21
  import { toolCommand } from "./commands/tool.js";
22
+ import { apiCommand } from "./commands/api.js";
21
23
  import { opsCliCommands } from "./commands/ops.js";
22
24
  import { getBanner } from "./banner.js";
23
25
  import { runRootAction } from "./root-action.js";
@@ -42,6 +44,7 @@ export function runCli(mcpCommand) {
42
44
  program.addCommand(installCommand);
43
45
  program.addCommand(loginCommand);
44
46
  program.addCommand(logoutCommand);
47
+ program.addCommand(tokenCommand);
45
48
  program.addCommand(statusCommand);
46
49
  program.addCommand(runCommand);
47
50
  program.addCommand(openCommand);
@@ -58,6 +61,7 @@ export function runCli(mcpCommand) {
58
61
  program.addCommand(eventsCommand);
59
62
  program.addCommand(previewCommand);
60
63
  program.addCommand(toolCommand);
64
+ program.addCommand(apiCommand);
61
65
  // The 4.0 operation catalog (`summer <area> <action>`); SUMMER_SURFACE=v3 leaves it out.
62
66
  if (resolveSurface(undefined) === "v4") {
63
67
  for (const command of opsCliCommands())
@@ -74,7 +74,6 @@ export type EventsPollParams = {
74
74
  * truncated:true when the page was full. */
75
75
  limit?: number;
76
76
  };
77
- /** rebind() could not re-read engine health; the session identity is unchanged. */
78
77
  export declare class EngineRebindError extends Error {
79
78
  constructor(message: string);
80
79
  }
@@ -152,6 +151,11 @@ export declare class EngineApiClient {
152
151
  * feed its ONE response in here instead of issuing a second request.
153
152
  */
154
153
  private _resolveQueuedResponse;
154
+ /**
155
+ * One Studio page op in the app's own Studio tab (capabilities.studioPage):
156
+ * POST /api/studio {op, input} -> 202, then its one terminal envelope.
157
+ */
158
+ studioPage(op: string, input: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
155
159
  health(): Promise<unknown>;
156
160
  /**
157
161
  * One long-poll of GET /api/events/poll (the engine's events channel). The
@@ -3,6 +3,7 @@ import { tmpdir } from "os";
3
3
  import { join } from "path";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { checkEngineHealth, discoverRegistryConnection, engineNotRunningError, engineSelectionFromEnv, getApiPort, getApiToken, resolveEngineConnection, } from "./engine.js";
6
+ import { AGENT_HOST_HEADER, resolveAgentHost } from "./agent-host.js";
6
7
  import { asRecord, boolFrom, numberFrom, stringFrom } from "./util/json.js";
7
8
  import { classifyOpsResponse, pollOpToTerminal, } from "./async-op-lifecycle.js";
8
9
  import { pickPlayDeterminism } from "./capabilities/play-determinism.js";
@@ -56,6 +57,16 @@ function withoutImageData(payload) {
56
57
  return metadata;
57
58
  }
58
59
  /** rebind() could not re-read engine health; the session identity is unchanged. */
60
+ /**
61
+ * The agent app driving the engine (Claude Code, Codex, ...), so the person's
62
+ * editor can say who is working ("Claude Code · Placing 12 trees"). Local
63
+ * only: the engine on this computer reads it for its activity line; it never
64
+ * leaves the machine from here, so the telemetry opt-out does not apply.
65
+ */
66
+ function engineAgentHeader() {
67
+ const host = resolveAgentHost().id;
68
+ return host && host !== "unknown" ? { [AGENT_HOST_HEADER]: host } : {};
69
+ }
59
70
  export class EngineRebindError extends Error {
60
71
  constructor(message) {
61
72
  super(message);
@@ -254,6 +265,7 @@ export class EngineApiClient {
254
265
  const headers = {
255
266
  Authorization: `Bearer ${this.token}`,
256
267
  "Content-Type": "application/json",
268
+ ...engineAgentHeader(),
257
269
  };
258
270
  const res = await fetch(url, {
259
271
  method,
@@ -282,6 +294,7 @@ export class EngineApiClient {
282
294
  headers: {
283
295
  Authorization: `Bearer ${this.token}`,
284
296
  "Content-Type": "application/json",
297
+ ...engineAgentHeader(),
285
298
  },
286
299
  body: body !== undefined ? JSON.stringify(body) : undefined,
287
300
  signal: AbortSignal.timeout(timeoutMs),
@@ -404,6 +417,14 @@ export class EngineApiClient {
404
417
  };
405
418
  }
406
419
  }
420
+ /**
421
+ * One Studio page op in the app's own Studio tab (capabilities.studioPage):
422
+ * POST /api/studio {op, input} -> 202, then its one terminal envelope.
423
+ */
424
+ async studioPage(op, input, timeoutMs = 90_000) {
425
+ const path = this.getEngineCapabilities()?.studioPage?.path ?? "/api/studio";
426
+ return this._requestQueued("POST", path, { op, input }, timeoutMs);
427
+ }
407
428
  async health() {
408
429
  return this.request("GET", "/api/health");
409
430
  }
@@ -23,7 +23,7 @@ interface DoctorOptions {
23
23
  }
24
24
  export declare function runDoctor(options?: DoctorOptions): Promise<DoctorResult>;
25
25
  /** The oldest Node major the package supports: `engines.node` in package.json. */
26
- export declare const MIN_NODE_MAJOR = 20;
26
+ export declare const MIN_NODE_MAJOR = 22;
27
27
  export declare function checkNodeVersion(nodeVersion?: string): DoctorCheck;
28
28
  export declare function checkLogin(): Promise<DoctorCheck>;
29
29
  /**
@@ -49,7 +49,7 @@ export async function runDoctor(options = {}) {
49
49
  return result;
50
50
  }
51
51
  /** The oldest Node major the package supports: `engines.node` in package.json. */
52
- export const MIN_NODE_MAJOR = 20;
52
+ export const MIN_NODE_MAJOR = 22;
53
53
  export function checkNodeVersion(nodeVersion = process.versions.node) {
54
54
  const major = Number(nodeVersion.split(".")[0]);
55
55
  const shown = `v${nodeVersion.replace(/^v/, "")}`;
@@ -16,14 +16,14 @@ export declare const gameTaskPlanInputSchema: z.ZodObject<{
16
16
  verification: z.ZodDefault<z.ZodEnum<["none", "fast", "full"]>>;
17
17
  }, "strip", z.ZodTypeAny, {
18
18
  mode: "auto" | "ship" | "debug" | "asset" | "new-game" | "feature" | "playtest" | "polish";
19
- target: "auto" | "audio" | "level" | "npc" | "ui" | "2d" | "3d" | "animation" | "multiplayer";
19
+ target: "auto" | "audio" | "level" | "npc" | "ui" | "3d" | "2d" | "animation" | "multiplayer";
20
20
  goal: string;
21
21
  assetPolicy: "reuse-first" | "ask-before-paid-generation" | "no-paid-generation" | "generate-when-clearly-needed";
22
22
  verification: "none" | "full" | "fast";
23
23
  }, {
24
24
  goal: string;
25
25
  mode?: "auto" | "ship" | "debug" | "asset" | "new-game" | "feature" | "playtest" | "polish" | undefined;
26
- target?: "auto" | "audio" | "level" | "npc" | "ui" | "2d" | "3d" | "animation" | "multiplayer" | undefined;
26
+ target?: "auto" | "audio" | "level" | "npc" | "ui" | "3d" | "2d" | "animation" | "multiplayer" | undefined;
27
27
  assetPolicy?: "reuse-first" | "ask-before-paid-generation" | "no-paid-generation" | "generate-when-clearly-needed" | undefined;
28
28
  verification?: "none" | "full" | "fast" | undefined;
29
29
  }>;
@@ -126,6 +126,9 @@ export declare function isAllowedNavigationOrigin(origin: string): boolean;
126
126
  * escapes, nothing that could name a file outside the project. Returns the
127
127
  * problem, or null when the path is acceptable. */
128
128
  export declare function resPathProblem(value: string): string | null;
129
+ /** A summerengine.com Studio path (`/studio`, `/studio/games`, `/studio?tab=assets`), the
130
+ * only paths the engine's `studio` Navigate row opens. */
131
+ export declare function isStudioPath(path: string): boolean;
129
132
  export declare function tokenize(text: string): string[];
130
133
  /** Rank every target for a free-text query. Exported for tests. */
131
134
  export declare function rankTargets(query: string, surface?: OpenSurface): OpenMatch[];
@@ -87,6 +87,50 @@ export function resPathProblem(value) {
87
87
  return "must not contain control characters";
88
88
  return null;
89
89
  }
90
+ /** A summerengine.com Studio path (`/studio`, `/studio/games`, `/studio?tab=assets`), the
91
+ * only paths the engine's `studio` Navigate row opens. */
92
+ export function isStudioPath(path) {
93
+ return /^\/studio(?:[/?#]|$)/.test(path);
94
+ }
95
+ /**
96
+ * A Studio path opens in the Summer app's own Studio tab when the running
97
+ * engine advertises the `studio` Navigate destination (it loads the tab when
98
+ * the person has not opened it yet). Otherwise, the engine off, an older
99
+ * engine or a refused Navigate, null: the caller opens the browser as before.
100
+ */
101
+ async function openInAppStudio(deps, path, url, extra) {
102
+ if (!isStudioPath(path))
103
+ return null;
104
+ let client;
105
+ try {
106
+ client = await deps.engine();
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ if (!advertisedNavigation(client)?.has("studio"))
112
+ return null;
113
+ const op = { op: "Navigate", target: "studio", path };
114
+ let receipt;
115
+ try {
116
+ receipt = await client.executeOps([op]);
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ if (extractOpError(receipt))
122
+ return null;
123
+ return {
124
+ ok: true,
125
+ action: "opened",
126
+ url,
127
+ ...extra,
128
+ op,
129
+ engine: { running: true, version: client.getEngineVersion?.(), navigation: true },
130
+ receipt,
131
+ hint: "Opened in the Studio tab of the Summer app, not a browser.",
132
+ };
133
+ }
90
134
  /** Launch the browser; a failed launch is a result, not a crash. */
91
135
  async function launch(deps, url) {
92
136
  try {
@@ -444,6 +488,9 @@ export async function runOpen(args, deps) {
444
488
  };
445
489
  if (print)
446
490
  return base;
491
+ const inApp = await openInAppStudio(deps, resolution.url, url, { unmapped: true });
492
+ if (inApp)
493
+ return inApp;
447
494
  const failure = await launch(deps, url);
448
495
  if (failure)
449
496
  return openFailed(url, failure, { unmapped: true });
@@ -479,6 +526,12 @@ export async function runOpen(args, deps) {
479
526
  };
480
527
  if (print)
481
528
  return result;
529
+ if (target.web.origin !== "docs") {
530
+ // The app's Studio tab is signed in with the app: no login detour.
531
+ const inApp = await openInAppStudio(deps, path, url, { target: summary });
532
+ if (inApp)
533
+ return inApp;
534
+ }
482
535
  const toOpen = needsLogin && loggedIn === false ? loginUrl : url;
483
536
  const failure = await launch(deps, toOpen);
484
537
  if (failure)
@@ -143,7 +143,8 @@ sessionFolder = process.cwd()) {
143
143
  }
144
144
  let client;
145
145
  try {
146
- client = await openProjectInEditor(root, { cwd: root }, { install: input.install === true }, deps);
146
+ // The agent asked for this game: an editor with another one switches (J16.1).
147
+ client = await openProjectInEditor(root, { cwd: root }, { install: input.install === true, switchOpen: true }, deps);
147
148
  }
148
149
  catch (error) {
149
150
  if (error instanceof EngineInstallError)
@@ -141,18 +141,18 @@ export declare const runtimeSpawnArgsSchema: z.ZodObject<{
141
141
  action: "free" | "spawn";
142
142
  mode?: "free" | "queue_free" | undefined;
143
143
  name?: string | undefined;
144
+ path?: string | undefined;
144
145
  instance?: string | undefined;
145
146
  scene?: string | undefined;
146
- path?: string | undefined;
147
147
  parent?: string | undefined;
148
148
  props?: Record<string, string | number | boolean> | undefined;
149
149
  }, {
150
150
  action: "free" | "spawn";
151
151
  mode?: "free" | "queue_free" | undefined;
152
152
  name?: string | undefined;
153
+ path?: string | undefined;
153
154
  instance?: string | undefined;
154
155
  scene?: string | undefined;
155
- path?: string | undefined;
156
156
  parent?: string | undefined;
157
157
  props?: Record<string, string | number | boolean> | undefined;
158
158
  }>;
@@ -177,10 +177,10 @@ export declare const shotSheetArgsSchema: z.ZodObject<{
177
177
  bookmark_name?: string | undefined;
178
178
  }[];
179
179
  scenePath?: string | undefined;
180
+ view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
180
181
  compare_previous?: boolean | undefined;
181
182
  max_size?: number | undefined;
182
183
  save_to?: string | undefined;
183
- view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
184
184
  update_previous?: boolean | undefined;
185
185
  aspect?: number | undefined;
186
186
  }, {
@@ -192,10 +192,10 @@ export declare const shotSheetArgsSchema: z.ZodObject<{
192
192
  bookmark_name?: string | undefined;
193
193
  }[];
194
194
  scenePath?: string | undefined;
195
+ view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
195
196
  compare_previous?: boolean | undefined;
196
197
  max_size?: number | undefined;
197
198
  save_to?: string | undefined;
198
- view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
199
199
  update_previous?: boolean | undefined;
200
200
  aspect?: number | undefined;
201
201
  }>;
@@ -251,11 +251,11 @@ export declare const zoomArgsSchema: z.ZodObject<{
251
251
  camera_position?: string | undefined;
252
252
  camera_look_at?: string | undefined;
253
253
  fov?: number | undefined;
254
+ view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
254
255
  bookmark_name?: string | undefined;
255
256
  region?: number[] | undefined;
256
257
  max_size?: number | undefined;
257
258
  save_to?: string | undefined;
258
- view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
259
259
  mark?: number | undefined;
260
260
  reference_size?: number[] | undefined;
261
261
  pad?: number | undefined;
@@ -264,11 +264,11 @@ export declare const zoomArgsSchema: z.ZodObject<{
264
264
  camera_position?: string | undefined;
265
265
  camera_look_at?: string | undefined;
266
266
  fov?: number | undefined;
267
+ view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
267
268
  bookmark_name?: string | undefined;
268
269
  region?: number[] | undefined;
269
270
  max_size?: number | undefined;
270
271
  save_to?: string | undefined;
271
- view?: "lighting" | "beauty" | "unshaded" | "normals" | "overdraw" | "wireframe" | undefined;
272
272
  mark?: number | undefined;
273
273
  reference_size?: number[] | undefined;
274
274
  pad?: number | undefined;
@@ -16,6 +16,7 @@
16
16
  * as the single per-tool dispatch table so the handler bodies stop being two
17
17
  * mirrors as well.
18
18
  */
19
+ import { viewSetOp } from "./view-set.js";
19
20
  import { tmpdir } from "node:os";
20
21
  import { join } from "node:path";
21
22
  import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
@@ -79,6 +80,7 @@ import { captureGame, captureScene, captureViewport } from "./capture.js";
79
80
  // the scripting ones come with their op builders from ./scene-script.js.
80
81
  import { ALIGN_DISTRIBUTE_FALLBACK, GREP_FALLBACK, NAVIGATION_PROBE_FALLBACK, RUNTIME_NODE_FALLBACK, RUNTIME_TREE_FALLBACK, SNAP_TO_SURFACE_FALLBACK, SNAPSHOT_DIFF_FALLBACK, STARCAST_FALLBACK, TEST_PLACEMENT_FALLBACK, WORLD_SNAPSHOT_FALLBACK, } from "./engine-fallbacks.js";
81
82
  import { sendLibraryFeedback, } from "../feedback/client.js";
83
+ import { batchToolResult, cancelJob, checkBatch, createBatch, estimateJob, getBatch, getJob, jobToolResult, listModels, runJob, summerApiToolAnswer } from "../summer-api.js";
82
84
  // One copy of the batch op classification and the instantiate/batch engine
83
85
  // path, shared with the MCP face.
84
86
  import { PLACEMENT_FIELDS, SCENE_MUTATION_OPS, SCENE_QUERY_OPS, instantiateScene, runBatch, } from "./placement-batch.js";
@@ -1296,6 +1298,11 @@ export const TOOL_DISPATCH = [
1296
1298
  op.scenePath = args.scenePath;
1297
1299
  return requireEngineSuccess(await (await ctx.engine()).executeOps([op]));
1298
1300
  }),
1301
+ entry("summer_view_set", "Show the person a view, rail and focus in the editor", true, async (args, ctx) => {
1302
+ const frame = Array.isArray(args.frame) ? args.frame.filter((entry) => typeof entry === "string") : undefined;
1303
+ const op = viewSetOp({ view: str(args, "view"), rail: optStr(args, "rail"), focus: optStr(args, "focus"), frame });
1304
+ return requireEngineSuccess(await (await ctx.engine()).executeOps([op]));
1305
+ }),
1299
1306
  entry("summer_replace_node", "Replace a node with a different scene or type and verify the saved scene", true, async (args, ctx) => {
1300
1307
  // ONE implementation for both faces (core/capabilities/replace-node.ts):
1301
1308
  // persisted ops + read-back of the saved .tscn.
@@ -1767,6 +1774,36 @@ export const TOOL_DISPATCH = [
1767
1774
  throw new ToolResultError(result, result.hint);
1768
1775
  return result;
1769
1776
  }),
1777
+ // --- account ---
1778
+ entry("summer_api_models", "The Summer API's models with input and price", false, async (args) => summerApiToolAnswer(() => listModels({ ...(optStr(args, "model") ? { model: optStr(args, "model") } : {}), ...(optStr(args, "capability") ? { capability: optStr(args, "capability") } : {}) }))),
1779
+ entry("summer_api_estimate", "The exact price of one Summer API job; charges nothing", false, async (args) => summerApiToolAnswer(() => estimateJob(str(args, "model"), args.input ?? {}))),
1780
+ entry("summer_api_run", "Run one Summer API job with the Summer sign-in on this computer", false, async (args) => summerApiToolAnswer(async () => {
1781
+ const out = optStr(args, "out");
1782
+ const follow = args.wait === true || Boolean(out);
1783
+ const job = await runJob(str(args, "model"), args.input ?? {}, {
1784
+ ...(optStr(args, "quoteId") ? { quoteId: optStr(args, "quoteId") } : {}),
1785
+ ...(optStr(args, "idempotencyKey") ? { idempotencyKey: optStr(args, "idempotencyKey") } : {}),
1786
+ ...(follow ? { waitSeconds: 30 } : {}),
1787
+ });
1788
+ return jobToolResult(job, { wait: args.wait === true, ...(out ? { out } : {}) });
1789
+ })),
1790
+ entry("summer_api_batch", "Run up to 500 Summer API jobs at once (all or none), or read a batch", false, async (args) => summerApiToolAnswer(async () => {
1791
+ const out = optStr(args, "out");
1792
+ const follow = { wait: args.wait === true, ...(out ? { out } : {}) };
1793
+ const batchId = optStr(args, "batchId");
1794
+ if (batchId)
1795
+ return batchToolResult(await getBatch(batchId), follow);
1796
+ const key = optStr(args, "idempotencyKey");
1797
+ const batch = await createBatch(checkBatch(Array.isArray(args.jobs) ? args.jobs : []), key ? { idempotencyKey: key } : {});
1798
+ return batchToolResult(batch, follow);
1799
+ })),
1800
+ entry("summer_api_job", "Read, follow, download or cancel a Summer API job", false, async (args) => summerApiToolAnswer(async () => {
1801
+ const jobId = str(args, "jobId");
1802
+ if (args.cancel === true)
1803
+ return { job: await cancelJob(jobId) };
1804
+ const out = optStr(args, "out");
1805
+ return jobToolResult(await getJob(jobId), { wait: args.wait === true, ...(out ? { out } : {}) });
1806
+ })),
1770
1807
  // --- feedback ---
1771
1808
  entry("summer_library_feedback", "Report library entry outcomes so Summer can fix and re-rank them", false, async (args) => {
1772
1809
  if (!Array.isArray(args.reports) || args.reports.length === 0) {
@@ -88,16 +88,16 @@ export declare const uiActivateArgsSchema: z.ZodObject<{
88
88
  button: z.ZodOptional<z.ZodString>;
89
89
  }, "strip", z.ZodTypeAny, {
90
90
  value?: string | number | undefined;
91
- title?: string | undefined;
92
91
  path?: string | undefined;
92
+ title?: string | undefined;
93
93
  action?: "focus" | "press" | "select_tab" | "toggle" | "set_text" | "set_value" | "dismiss_dialog" | undefined;
94
94
  button?: string | undefined;
95
95
  index?: number | undefined;
96
96
  submit?: boolean | undefined;
97
97
  }, {
98
98
  value?: string | number | undefined;
99
- title?: string | undefined;
100
99
  path?: string | undefined;
100
+ title?: string | undefined;
101
101
  action?: "focus" | "press" | "select_tab" | "toggle" | "set_text" | "set_value" | "dismiss_dialog" | undefined;
102
102
  button?: string | undefined;
103
103
  index?: number | undefined;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * summer_view_set / engine.view_set (PRODUCT-SPEC J17): an outside agent shows
3
+ * the person what it works on, through the engine's SetView op: the view, the
4
+ * right rail, what to focus, and the nodes to frame the camera on.
5
+ */
6
+ export declare function viewSetOp(args: {
7
+ view: string;
8
+ rail?: string;
9
+ focus?: string;
10
+ frame?: string[];
11
+ }): Record<string, unknown>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * summer_view_set / engine.view_set (PRODUCT-SPEC J17): an outside agent shows
3
+ * the person what it works on, through the engine's SetView op: the view, the
4
+ * right rail, what to focus, and the nodes to frame the camera on.
5
+ */
6
+ export function viewSetOp(args) {
7
+ const op = { op: "SetView", view: args.view };
8
+ if (args.rail)
9
+ op.rail = args.rail;
10
+ if (args.focus)
11
+ op.focus = args.focus;
12
+ if (args.frame?.length)
13
+ op.frame = args.frame;
14
+ return op;
15
+ }
@@ -81,6 +81,9 @@ export interface EngineCapabilities {
81
81
  * destination ids this build can open. Absent = engine predates the op;
82
82
  * summer_open then falls back to the legacy per-surface ops it can map. */
83
83
  navigation?: EngineNavigationCapabilities;
84
+ /** The Studio page ops this app answers itself through its own Studio tab
85
+ * (POST /api/studio). Absent = send them to Summer's server. */
86
+ studioPage?: EngineStudioPageCapabilities;
84
87
  /** Launch postures this build ENFORCES: "focus" (always), "background"
85
88
  * (`--summer-background`), "offscreen" (`--summer-offscreen` /
86
89
  * `--summer-verify`). Non-macOS builds advertise ["focus"] only. Absent =
@@ -89,6 +92,11 @@ export interface EngineCapabilities {
89
92
  * snake_case spelling too. */
90
93
  launchPostures?: string[];
91
94
  }
95
+ export interface EngineStudioPageCapabilities {
96
+ version?: number;
97
+ path: string;
98
+ ops: string[];
99
+ }
92
100
  export interface EngineNavigationCapabilities {
93
101
  version?: number;
94
102
  targets: EngineNavigationTarget[];
@@ -49,6 +49,8 @@ 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
+ // engine.view_set: the frame's view, rail, focus and frame (core/capabilities/view-set.ts)
53
+ "SetView",
52
54
  // Summer Apps editor docks (summer_app_dock: core/capabilities/apps.ts)
53
55
  "OpenAppDock", "CloseAppDock", "ListAppDocks",
54
56
  // summer_enable_plugin (core/capabilities/enable-plugin.ts)
@@ -188,11 +190,27 @@ export function parseEngineCapabilities(raw) {
188
190
  const navigation = parseNavigation(record.navigation);
189
191
  if (navigation)
190
192
  out.navigation = navigation;
193
+ const studioPage = parseStudioPage(record.studioPage);
194
+ if (studioPage)
195
+ out.studioPage = studioPage;
191
196
  const launchPostures = stringList(record.launchPostures) ?? stringList(record.launch_postures);
192
197
  if (launchPostures)
193
198
  out.launchPostures = launchPostures;
194
199
  return Object.keys(out).length > 0 ? out : undefined;
195
200
  }
201
+ function parseStudioPage(raw) {
202
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
203
+ return undefined;
204
+ const record = raw;
205
+ const ops = stringList(record.ops);
206
+ if (!ops?.length)
207
+ return undefined;
208
+ return {
209
+ ...(typeof record.version === "number" ? { version: record.version } : {}),
210
+ path: typeof record.path === "string" && record.path.startsWith("/api/") ? record.path : "/api/studio",
211
+ ops,
212
+ };
213
+ }
196
214
  function parseNavigation(raw) {
197
215
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
198
216
  return undefined;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The browser sign-in for the Summer account (the `cli_session` flow): the
3
+ * person opens `{gateway}/login?cli_session=<id>`, signs in, and approves;
4
+ * this polls `{gateway}/api/auth/cli-login?session=<id>` until the gateway
5
+ * hands over the CLI token, then saves it to ~/.summer (the files the desktop
6
+ * engine and every tool read). `summer login` and the MCP's sign-in link
7
+ * (core/sign-in-link.ts) share it.
8
+ */
9
+ import { type LoginSession } from "./auth.js";
10
+ export declare const POLL_TIMEOUT_MS = 900000;
11
+ export interface CliSessionPollDependencies {
12
+ fetch: typeof fetch;
13
+ sleep: (ms: number) => Promise<void>;
14
+ now: () => number;
15
+ log: (message: string) => void;
16
+ /** Stop waiting (another sign-in already saved a token, the server is closing). */
17
+ cancelled?: () => boolean | Promise<boolean>;
18
+ }
19
+ /** The page the person opens to sign in. */
20
+ export declare function cliSessionLoginUrl(gatewayUrl: string, sessionId: string): string;
21
+ /**
22
+ * Wait for the browser sign-in of `sessionId`, save it, and return it.
23
+ * Throws on a rejected request, an invalid token, a timeout, or `cancelled`.
24
+ */
25
+ export declare function pollCliSession(gatewayUrl: string, sessionId: string, deps: CliSessionPollDependencies): Promise<LoginSession>;