summer-engine 2.7.0 → 2.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 (150) hide show
  1. package/.claude-plugin/plugin.json +2 -1
  2. package/.codex-plugin/plugin.json +3 -2
  3. package/.cursor-plugin/plugin.json +2 -1
  4. package/.opencode/INSTALL.md +1 -1
  5. package/.opencode/plugins/summer.js +1 -1
  6. package/AGENTS.md +4 -4
  7. package/CHANGELOG.md +10 -0
  8. package/GEMINI.md +5 -4
  9. package/README.md +58 -19
  10. package/assets/autopilot/README.md +51 -0
  11. package/assets/autopilot/autopilot.gd +222 -0
  12. package/assets/autopilot/probe_base.gd +262 -0
  13. package/assets/autopilot/run.sh +78 -0
  14. package/commands/gameskill.md +11 -2
  15. package/dist/bin/summer.js +8 -0
  16. package/dist/commands/config.d.ts +2 -0
  17. package/dist/commands/config.js +53 -0
  18. package/dist/commands/create.d.ts +1 -0
  19. package/dist/commands/create.js +38 -6
  20. package/dist/commands/list.js +1 -1
  21. package/dist/commands/login.d.ts +19 -0
  22. package/dist/commands/login.js +123 -36
  23. package/dist/commands/logout.js +3 -16
  24. package/dist/commands/logs.d.ts +2 -0
  25. package/dist/commands/logs.js +19 -0
  26. package/dist/commands/mcp.js +7 -2
  27. package/dist/commands/open.js +1 -1
  28. package/dist/commands/publish.d.ts +2 -0
  29. package/dist/commands/publish.js +20 -0
  30. package/dist/commands/releases.d.ts +2 -0
  31. package/dist/commands/releases.js +20 -0
  32. package/dist/commands/run.js +1 -1
  33. package/dist/lib/api-client.d.ts +4 -2
  34. package/dist/lib/api-client.js +28 -4
  35. package/dist/lib/auth.d.ts +40 -12
  36. package/dist/lib/auth.js +173 -37
  37. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  38. package/dist/lib/cloud/engine-bridge.js +32 -6
  39. package/dist/lib/config.d.ts +20 -0
  40. package/dist/lib/config.js +142 -0
  41. package/dist/lib/creator.d.ts +93 -0
  42. package/dist/lib/creator.js +393 -0
  43. package/dist/lib/engine.d.ts +35 -2
  44. package/dist/lib/engine.js +240 -6
  45. package/dist/lib/public-surface-paths.d.ts +13 -0
  46. package/dist/lib/public-surface-paths.js +42 -0
  47. package/dist/lib/skills-registry.d.ts +8 -0
  48. package/dist/lib/skills-registry.js +9 -0
  49. package/dist/lib/store.d.ts +14 -0
  50. package/dist/lib/store.js +145 -0
  51. package/dist/lib/summer-compatibility.d.ts +6 -0
  52. package/dist/lib/summer-compatibility.js +22 -0
  53. package/dist/mcp/server.d.ts +8 -1
  54. package/dist/mcp/server.js +25 -4
  55. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  56. package/dist/mcp/tools/creator-tools.js +105 -0
  57. package/dist/mcp/tools/generate-tools.js +16 -6
  58. package/dist/mcp/tools/project-tools.js +12 -7
  59. package/dist/mcp/tools/scene-tools.js +6 -6
  60. package/dist/mcp/tools/visual-tools.js +4 -3
  61. package/hooks/pre-commit-doctor.sh +0 -0
  62. package/hooks/run-hook.cmd +0 -0
  63. package/hooks/session-start.sh +5 -5
  64. package/package.json +3 -1
  65. package/references/collaborative-protocol.md +50 -0
  66. package/references/gd-style.md +121 -0
  67. package/references/godot-version.md +51 -0
  68. package/references/mcp-tools-reference.md +199 -0
  69. package/references/public-surface-language.md +30 -0
  70. package/references/summer-folder.md +90 -0
  71. package/references/template-registry.md +73 -0
  72. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  73. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  74. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  75. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  76. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  77. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  78. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  79. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  80. package/skills/3d-assets/character-model/SKILL.md +34 -19
  81. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  82. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  83. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  84. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  85. package/skills/README.md +1 -1
  86. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  87. package/skills/animation/animation-tree/SKILL.md +39 -15
  88. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  89. package/skills/animation/generate-motion/SKILL.md +47 -15
  90. package/skills/animation/procedural-animation/SKILL.md +31 -13
  91. package/skills/animation/retarget/SKILL.md +127 -79
  92. package/skills/audio/adaptive-music/SKILL.md +24 -20
  93. package/skills/audio/ambient-bed/SKILL.md +22 -16
  94. package/skills/audio/audio-direction/SKILL.md +15 -10
  95. package/skills/audio/music-track/SKILL.md +18 -12
  96. package/skills/audio/sound-effect/SKILL.md +17 -12
  97. package/skills/audio/voice-line/SKILL.md +42 -42
  98. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  99. package/skills/debugging/debug/SKILL.md +52 -20
  100. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  101. package/skills/deployment/remote-deploy/SKILL.md +56 -23
  102. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  103. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  104. package/skills/level-design/design-level/SKILL.md +22 -8
  105. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  106. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  107. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  108. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  109. package/skills/performance/tune-performance/SKILL.md +51 -32
  110. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  111. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  112. package/skills/scene-and-project/brainstorm-game/SKILL.md +3 -5
  113. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  114. package/skills/scene-and-project/play/SKILL.md +10 -4
  115. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  116. package/skills/scene-and-project/summer-cloud/SKILL.md +1 -1
  117. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  118. package/skills/video/animated-loop/SKILL.md +63 -39
  119. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  120. package/skills/video/trailer-shot/SKILL.md +30 -21
  121. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  122. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  123. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  124. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  125. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  126. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  127. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  128. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  129. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  130. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  131. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  132. package/skills/workflow/brainstorming/SKILL.md +1 -1
  133. package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
  134. package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
  135. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  136. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  137. package/skills/workflow/gameskill/SKILL.md +11 -2
  138. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  139. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  140. package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
  141. package/skills/workflow/mcpupdate/SKILL.md +3 -3
  142. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  143. package/skills/workflow/skill-create/SKILL.md +7 -7
  144. package/skills/workflow/skill-test/SKILL.md +3 -3
  145. package/skills/workflow/using-summer/SKILL.md +12 -2
  146. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  147. package/skills/workflow/writing-plans/SKILL.md +7 -7
  148. package/skills/workflow/writing-skills/SKILL.md +7 -5
  149. package/skills/workflow/writing-skills/render-graphs.js +0 -0
  150. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -1,49 +1,143 @@
1
1
  import { Command } from "commander";
2
2
  import { randomUUID } from "crypto";
3
3
  import open from "open";
4
- import { getAuthToken, saveAuthToken, saveCloudToken, saveUserInfo } from "../lib/auth.js";
5
- const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
4
+ import { getAuthToken, getCreatorToken, saveCreatorToken, saveLoginSession, } from "../lib/auth.js";
5
+ import { getCreatorApiUrl, getGatewayUrl } from "../lib/config.js";
6
6
  const POLL_INTERVAL_MS = 2000;
7
7
  const POLL_TIMEOUT_MS = 120000;
8
8
  export const loginCommand = new Command("login")
9
9
  .description("Sign in to Summer Engine via your browser")
10
+ .option("--creator", "Connect a separately scoped Summercraft creator token for publishing")
10
11
  .option("--force", "Force re-authentication even if already logged in")
11
12
  .action(async (opts) => {
13
+ if (opts.creator) {
14
+ const existingCreator = await getCreatorToken();
15
+ if (existingCreator && !opts.force) {
16
+ console.log("A creator token is already connected. Use --creator --force to replace it.");
17
+ return;
18
+ }
19
+ await runCreatorLogin();
20
+ return;
21
+ }
12
22
  const existing = await getAuthToken();
13
23
  if (existing && !opts.force) {
14
24
  console.log("Already logged in. Use --force to re-authenticate.");
15
25
  return;
16
26
  }
17
- await doLogin();
27
+ await runLogin();
18
28
  });
19
- async function doLogin() {
20
- const sessionId = randomUUID();
21
- const loginUrl = `${GATEWAY_URL}/login?cli_session=${sessionId}`;
22
- console.log("Sign in at: " + loginUrl);
23
- console.log("");
29
+ async function readHiddenLine(prompt) {
30
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
31
+ process.stdout.write(prompt);
32
+ let value = "";
33
+ for await (const chunk of process.stdin)
34
+ value += String(chunk);
35
+ return value.trim();
36
+ }
37
+ process.stdout.write(prompt);
38
+ const input = process.stdin;
39
+ const wasRaw = input.isRaw;
40
+ input.setRawMode(true);
41
+ input.resume();
42
+ return new Promise((resolve, reject) => {
43
+ let value = "";
44
+ const finish = (error) => {
45
+ input.off("data", onData);
46
+ input.setRawMode(Boolean(wasRaw));
47
+ input.pause();
48
+ process.stdout.write("\n");
49
+ if (error)
50
+ reject(error);
51
+ else
52
+ resolve(value.trim());
53
+ };
54
+ const onData = (chunk) => {
55
+ for (const char of String(chunk)) {
56
+ if (char === "\u0003") {
57
+ finish(new Error("Creator token entry cancelled."));
58
+ return;
59
+ }
60
+ if (char === "\r" || char === "\n" || char === "\u0004") {
61
+ finish();
62
+ return;
63
+ }
64
+ if (char === "\u007f" || char === "\b") {
65
+ value = value.slice(0, -1);
66
+ }
67
+ else if (char >= " ") {
68
+ value += char;
69
+ }
70
+ }
71
+ };
72
+ input.on("data", onData);
73
+ });
74
+ }
75
+ const defaultCreatorLoginDependencies = {
76
+ openUrl: open,
77
+ readSecret: readHiddenLine,
78
+ log: console.log,
79
+ };
80
+ /**
81
+ * The browser session mints the token; the token is pasted once into a hidden
82
+ * prompt. This deliberately does not replace the core Summer login JWT.
83
+ */
84
+ export async function runCreatorLogin(overrides = {}) {
85
+ const deps = { ...defaultCreatorLoginDependencies, ...overrides };
86
+ const settingsUrl = `${await getCreatorApiUrl()}/creator/settings/tokens`;
87
+ deps.log(`Opening creator token settings: ${settingsUrl}`);
88
+ deps.log('Mint a token with the exact "publish" scope, copy its one-time sc_ value, then return here.');
24
89
  try {
25
- await open(loginUrl);
90
+ await deps.openUrl(settingsUrl);
26
91
  }
27
92
  catch {
28
- console.log("Could not open browser. Copy the URL above and open it manually.");
29
- console.log("");
93
+ deps.log("Could not open the browser. Copy the URL above and open it manually.");
30
94
  }
31
- console.log("Waiting for authentication...");
32
- const pollUrl = `${GATEWAY_URL}/api/auth/cli-login?session=${sessionId}`;
33
- const startTime = Date.now();
95
+ const token = await deps.readSecret("Creator token (input hidden): ");
96
+ await saveCreatorToken(token);
97
+ deps.log("Creator publishing connected. Your core Summer login token was not changed.");
98
+ }
99
+ const defaultDependencies = {
100
+ fetch,
101
+ openUrl: open,
102
+ randomId: randomUUID,
103
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
104
+ now: Date.now,
105
+ log: console.log,
106
+ };
107
+ export async function runLogin(overrides = {}) {
108
+ const deps = { ...defaultDependencies, ...overrides };
109
+ const gatewayUrl = await getGatewayUrl();
110
+ const sessionId = deps.randomId();
111
+ const loginUrl = `${gatewayUrl}/login?cli_session=${encodeURIComponent(sessionId)}`;
112
+ deps.log("Sign in at: " + loginUrl);
113
+ deps.log("");
114
+ try {
115
+ await deps.openUrl(loginUrl);
116
+ }
117
+ catch {
118
+ deps.log("Could not open browser. Copy the URL above and open it manually.");
119
+ deps.log("");
120
+ }
121
+ deps.log("Waiting for authentication...");
122
+ const pollUrl = `${gatewayUrl}/api/auth/cli-login?session=${encodeURIComponent(sessionId)}`;
123
+ const startTime = deps.now();
34
124
  let lastError = null;
35
- while (Date.now() - startTime < POLL_TIMEOUT_MS) {
36
- await sleep(POLL_INTERVAL_MS);
125
+ while (deps.now() - startTime < POLL_TIMEOUT_MS) {
126
+ await deps.sleep(POLL_INTERVAL_MS);
37
127
  try {
38
- const res = await fetch(pollUrl, {
128
+ const res = await deps.fetch(pollUrl, {
39
129
  signal: AbortSignal.timeout(5000),
40
130
  });
41
131
  if (!res.ok) {
42
132
  if (res.status === 503) {
43
- lastError = "Server not ready (Redis/auth not configured). Try again later.";
133
+ lastError =
134
+ "The login service is unavailable (Redis/auth is not configured).";
135
+ }
136
+ else if (res.status >= 400 && res.status < 500) {
137
+ lastError = `The login request was rejected (${res.status}).`;
44
138
  }
45
139
  else if (res.status >= 500) {
46
- lastError = `Server error (${res.status}). Try again later.`;
140
+ lastError = `The login service failed (${res.status}).`;
47
141
  }
48
142
  continue;
49
143
  }
@@ -51,28 +145,21 @@ async function doLogin() {
51
145
  if (data.status === "pending")
52
146
  continue;
53
147
  if (data.status === "complete" && data.token) {
54
- await saveAuthToken(data.token);
55
- if (data.cloudToken) {
56
- await saveCloudToken(data.cloudToken);
57
- }
58
- if (data.user) {
59
- await saveUserInfo(data.user);
60
- }
61
- console.log(`\nLogged in as ${data.user?.email || "unknown"}`);
148
+ await saveLoginSession({
149
+ token: data.token,
150
+ cloudToken: data.cloudToken,
151
+ user: data.user,
152
+ scopes: data.scopes,
153
+ cloudScopes: data.cloudScopes,
154
+ });
155
+ deps.log(`\nLogged in as ${data.user?.email || "unknown"}`);
62
156
  return;
63
157
  }
158
+ lastError = "The login service returned an incomplete authentication result.";
64
159
  }
65
160
  catch (err) {
66
161
  lastError = err instanceof Error ? err.message : "Network error";
67
162
  }
68
163
  }
69
- console.error("\nLogin timed out. Please try again.");
70
- if (lastError) {
71
- console.error(`Last error: ${lastError}`);
72
- }
73
- console.error("\nDid you click \"Yes, Sign In\" in the browser after opening the URL?");
74
- process.exit(1);
75
- }
76
- function sleep(ms) {
77
- return new Promise((resolve) => setTimeout(resolve, ms));
164
+ throw new Error(`Login timed out after 120 seconds.${lastError ? ` Last error: ${lastError}` : ""} Recovery: run "summer login" again, complete "Yes, Sign In" in the browser, and return to the terminal within two minutes.`);
78
165
  }
@@ -1,23 +1,10 @@
1
1
  import { Command } from "commander";
2
- import { unlink } from "fs/promises";
3
- import { join } from "path";
4
- import { getSummerDir } from "../lib/auth.js";
2
+ import { clearAuthCredentials } from "../lib/auth.js";
5
3
  export const logoutCommand = new Command("logout")
6
4
  .description("Sign out and clear stored auth tokens")
7
5
  .action(async () => {
8
- const dir = getSummerDir();
9
- const files = ["auth-token", "cloud-token", "user.json"];
10
- let cleared = false;
11
- for (const file of files) {
12
- try {
13
- await unlink(join(dir, file));
14
- cleared = true;
15
- }
16
- catch {
17
- // File doesn't exist, that's fine
18
- }
19
- }
20
- if (cleared) {
6
+ const removed = await clearAuthCredentials();
7
+ if (removed > 0) {
21
8
  console.log("Logged out. Auth tokens cleared.");
22
9
  }
23
10
  else {
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const logsCommand: Command;
@@ -0,0 +1,19 @@
1
+ import { Command } from "commander";
2
+ import { readCreatorLogs } from "../lib/creator.js";
3
+ export const logsCommand = new Command("logs")
4
+ .description("Read creator runtime logs")
5
+ .option("--project-id <id>", "Creator project ID")
6
+ .option("--release <id>", "Restrict logs to one release")
7
+ .option("--limit <count>", "Maximum log entries to return", "100")
8
+ .action(async (opts) => {
9
+ const limit = Number.parseInt(opts.limit, 10);
10
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {
11
+ throw new Error("--limit must be between 1 and 1000. Recovery: pass a whole number in that range.");
12
+ }
13
+ await readCreatorLogs({
14
+ projectId: opts.projectId,
15
+ releaseId: opts.release,
16
+ limit,
17
+ face: "cli",
18
+ });
19
+ });
@@ -3,8 +3,13 @@ import { configureAgentMcp, parseAgent, parseScope, supportedAgents, } from "../
3
3
  import { startMcpServer } from "../mcp/server.js";
4
4
  export const mcpCommand = new Command("mcp")
5
5
  .description("Start the MCP server for AI tool integration (Cursor, Claude Code, etc.)")
6
- .action(async () => {
7
- await startMcpServer();
6
+ .option("--project <path>", "Bind local engine tools to the editor running this project")
7
+ .option("--instance <id>", "Bind local engine tools to one exact Summer editor instance")
8
+ .action(async (opts) => {
9
+ await startMcpServer({
10
+ projectPath: opts.project,
11
+ instanceId: opts.instance,
12
+ });
8
13
  });
9
14
  mcpCommand
10
15
  .command("setup <agent>")
@@ -13,7 +13,7 @@ export const openCommand = new Command("open")
13
13
  }
14
14
  if (!existsSync(`${fullPath}/project.godot`)) {
15
15
  console.error(`No project.godot found in ${fullPath}\n` +
16
- "This doesn't look like a Godot/Summer Engine project.\n" +
16
+ "This doesn't look like a Summer Engine project.\n" +
17
17
  "Create one with: summer create 3d-basic my-game");
18
18
  process.exit(1);
19
19
  }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const publishCommand: Command;
@@ -0,0 +1,20 @@
1
+ import { Command } from "commander";
2
+ import { publishCreator } from "../lib/creator.js";
3
+ export const publishCommand = new Command("publish")
4
+ .description("Publish a confirmed creator release")
5
+ .argument("[project]", "Project root. Defaults to the current directory.")
6
+ .requiredOption("--artifact <path>", "Exact exported Summer .pck artifact")
7
+ .requiredOption("--version <value>", "Immutable release version")
8
+ .option("--manifest <path>", "Optional JSON release manifest")
9
+ .option("--project-id <id>", "Creator project ID")
10
+ .option("--channel <name>", "Release channel")
11
+ .option("--notes <text>", "Release notes")
12
+ .option("--confirm", "Confirm the exact project and channel after reviewing them")
13
+ .action(async (project, opts) => {
14
+ const result = await publishCreator({
15
+ project,
16
+ ...opts,
17
+ face: "cli",
18
+ });
19
+ console.log(JSON.stringify(result, null, 2));
20
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const releasesCommand: Command;
@@ -0,0 +1,20 @@
1
+ import { Command } from "commander";
2
+ import { listCreatorReleases } from "../lib/creator.js";
3
+ export const releasesCommand = new Command("releases")
4
+ .description("List creator releases for a project")
5
+ .option("--project-id <id>", "Creator project ID")
6
+ .option("--limit <count>", "Maximum releases to return", "20")
7
+ .option("--cursor <value>", "Opaque cursor returned by the previous page")
8
+ .action(async (opts) => {
9
+ const limit = Number.parseInt(opts.limit, 10);
10
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
11
+ throw new Error("--limit must be between 1 and 100. Recovery: pass a whole number in that range.");
12
+ }
13
+ const result = await listCreatorReleases({
14
+ projectId: opts.projectId,
15
+ limit,
16
+ cursor: opts.cursor,
17
+ face: "cli",
18
+ });
19
+ console.log(JSON.stringify(result, null, 2));
20
+ });
@@ -100,7 +100,7 @@ export const runCommand = new Command("run")
100
100
  }
101
101
  if (!existsSync(join(fullProjectPath, "project.godot"))) {
102
102
  console.error(`No project.godot found in ${fullProjectPath}\n` +
103
- "This doesn't look like a Godot/Summer Engine project.");
103
+ "This doesn't look like a Summer Engine project.");
104
104
  process.exit(1);
105
105
  }
106
106
  }
@@ -1,3 +1,4 @@
1
+ import { type EngineSelection } from "./engine.js";
1
2
  export type EngineSnapshot = {
2
3
  ok: boolean;
3
4
  localPath?: string;
@@ -42,8 +43,9 @@ export declare class EngineApiClient {
42
43
  private port;
43
44
  private token;
44
45
  private targetIdentity;
45
- constructor(port: number, token: string, targetIdentity?: EngineTargetIdentity | string);
46
- static connect(): Promise<EngineApiClient>;
46
+ private selection;
47
+ constructor(port: number, token: string, targetIdentity?: EngineTargetIdentity | string, selection?: EngineSelection | null);
48
+ static connect(selection?: EngineSelection): Promise<EngineApiClient>;
47
49
  /** The projectIdHash this session is bound to (undefined if none was reported
48
50
  * at connect — e.g. no project open yet). */
49
51
  getBoundProjectIdHash(): string | undefined;
@@ -2,7 +2,7 @@ import { mkdir, writeFile } from "fs/promises";
2
2
  import { tmpdir } from "os";
3
3
  import { join } from "path";
4
4
  import { randomUUID } from "node:crypto";
5
- import { getApiToken, getApiPort, checkEngineHealth } from "./engine.js";
5
+ import { getApiToken, getApiPort, checkEngineHealth, resolveEngineConnection, } from "./engine.js";
6
6
  import { classifyOpsResponse, pollOpToTerminal, } from "./async-op-lifecycle.js";
7
7
  function asRecord(value) {
8
8
  return value && typeof value === "object" && !Array.isArray(value)
@@ -74,15 +74,25 @@ export class EngineApiClient {
74
74
  // preserves the pre-2.6.6 constructor contract for existing callers that only
75
75
  // supplied a projectIdHash.
76
76
  targetIdentity;
77
- constructor(port, token, targetIdentity = {}) {
77
+ selection;
78
+ constructor(port, token, targetIdentity = {}, selection = null) {
78
79
  this.port = port;
79
80
  this.token = token;
80
81
  this.targetIdentity =
81
82
  typeof targetIdentity === "string"
82
83
  ? { projectIdHash: targetIdentity }
83
84
  : { ...targetIdentity };
84
- }
85
- static async connect() {
85
+ this.selection = selection ? { ...selection } : null;
86
+ }
87
+ static async connect(selection) {
88
+ if (selection) {
89
+ const connection = await resolveEngineConnection(selection);
90
+ return new EngineApiClient(connection.port, connection.token, {
91
+ instanceId: connection.health.instanceId,
92
+ projectId: connection.health.projectId,
93
+ projectIdHash: connection.health.projectIdHash,
94
+ }, { ...selection });
95
+ }
86
96
  const port = await getApiPort();
87
97
  const token = await getApiToken();
88
98
  if (!token) {
@@ -558,6 +568,20 @@ export class EngineApiClient {
558
568
  * will fail and trigger a reconnect+retry if the client really is stale.
559
569
  */
560
570
  async credentialsChanged() {
571
+ if (this.selection) {
572
+ try {
573
+ const connection = await resolveEngineConnection(this.selection);
574
+ return (connection.port !== this.port ||
575
+ connection.token !== this.token ||
576
+ connection.health.instanceId !== this.targetIdentity.instanceId);
577
+ }
578
+ catch {
579
+ // A selected editor disappearing is real drift. Dropping the cached
580
+ // client makes the next connect report which project/instance is absent
581
+ // instead of silently following another editor's global pointer.
582
+ return true;
583
+ }
584
+ }
561
585
  try {
562
586
  const [port, token] = await Promise.all([getApiPort(), getApiToken()]);
563
587
  if (!token)
@@ -1,15 +1,43 @@
1
- export declare function getSummerDir(): string;
2
- export declare function getAuthToken(): Promise<string | null>;
3
- export declare function saveAuthToken(token: string): Promise<void>;
4
- export declare function getCloudToken(): Promise<string | null>;
5
- export declare function saveCloudToken(token: string): Promise<void>;
6
- export declare function getUserInfo(): Promise<{
1
+ export { getSummerDir } from "./store.js";
2
+ export interface SummerUserInfo {
7
3
  id: string;
8
4
  email: string;
9
5
  name?: string;
10
- } | null>;
11
- export declare function saveUserInfo(info: {
12
- id: string;
13
- email: string;
14
- name?: string;
15
- }): Promise<void>;
6
+ }
7
+ export interface CredentialMetadata {
8
+ audience: string[];
9
+ scopes: string[];
10
+ tokenType?: string;
11
+ issuedAt?: string;
12
+ expiresAt?: string;
13
+ }
14
+ interface CredentialMetadataDocument {
15
+ schemaVersion: 1;
16
+ auth?: CredentialMetadata;
17
+ cloud?: CredentialMetadata;
18
+ creator?: CredentialMetadata;
19
+ }
20
+ export interface LoginSession {
21
+ token: string;
22
+ cloudToken?: string | null;
23
+ user?: SummerUserInfo;
24
+ scopes?: string[];
25
+ cloudScopes?: string[];
26
+ }
27
+ export declare function getAuthToken(): Promise<string | null>;
28
+ export declare function saveAuthToken(token: string, scopes?: string[]): Promise<void>;
29
+ export declare function getCloudToken(): Promise<string | null>;
30
+ export declare function saveCloudToken(token: string, scopes?: string[]): Promise<void>;
31
+ /**
32
+ * Summercraft creator tokens are a separate credential audience from the
33
+ * core-compatible Summer CLI JWT. Never store an sc_ token in auth-token:
34
+ * the engine reads that file and expects the summer-cli JWT contract.
35
+ */
36
+ export declare function getCreatorToken(): Promise<string | null>;
37
+ export declare function saveCreatorToken(token: string): Promise<void>;
38
+ export declare function getUserInfo(): Promise<SummerUserInfo | null>;
39
+ export declare function saveUserInfo(info: SummerUserInfo): Promise<void>;
40
+ export declare function saveLoginSession(session: LoginSession): Promise<void>;
41
+ export declare function getCredentialMetadata(): Promise<CredentialMetadataDocument>;
42
+ export declare function clearAuthCredentials(): Promise<number>;
43
+ export declare function assertCredentialScopes(metadata: CredentialMetadata | undefined, requiredScopes: string[]): void;