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
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "summer",
12
12
  "source": "./",
13
- "version": "4.6.0",
13
+ "version": "4.8.0",
14
14
  "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. A first-run guide (/summer:start), game-dev skills, and two MCP servers: the bridge to the Summer Engine desktop app, and Summer's hosted cloud server for people without it.",
15
15
  "author": {
16
16
  "name": "Summer Engine",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "summer",
4
4
  "displayName": "Summer Engine",
5
- "version": "4.6.0",
5
+ "version": "4.8.0",
6
6
  "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. A first-run guide (/summer:start), game-dev skills, and two MCP servers: the bridge to the Summer Engine desktop app, and Summer's hosted cloud server for people without it.",
7
7
  "author": {
8
8
  "name": "Summer Engine",
@@ -35,7 +35,6 @@
35
35
  "./library/skills/3d-lighting/",
36
36
  "./library/skills/adaptive-music/",
37
37
  "./library/skills/agent-playtesting/",
38
- "./library/skills/ai-characters-in-games/",
39
38
  "./library/skills/ambient-bed/",
40
39
  "./library/skills/animated-loop/",
41
40
  "./library/skills/animation-tree/",
@@ -115,6 +114,7 @@
115
114
  "./library/skills/store-art/",
116
115
  "./library/skills/store-listing/",
117
116
  "./library/skills/summer-analytics/",
117
+ "./library/skills/summer-api/",
118
118
  "./library/skills/summer-apps/",
119
119
  "./library/skills/summer-apps-design/",
120
120
  "./library/skills/summer-assets/",
@@ -125,6 +125,7 @@
125
125
  "./library/skills/summer-matchmaking/",
126
126
  "./library/skills/summer-parties/",
127
127
  "./library/skills/summer-player-data/",
128
+ "./library/skills/summer-router/",
128
129
  "./library/skills/summer-store/",
129
130
  "./library/skills/summer-world-chat/",
130
131
  "./library/skills/summer-world-saves/",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/codex — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "version": "4.6.0",
5
- "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
4
+ "version": "4.8.0",
5
+ "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 131-tool MCP bridge to the Summer Engine desktop app.",
6
6
  "author": {
7
7
  "name": "Summer Engine",
8
8
  "email": "founders@summerengine.com",
@@ -25,7 +25,6 @@
25
25
  "./library/skills/3d-lighting/",
26
26
  "./library/skills/adaptive-music/",
27
27
  "./library/skills/agent-playtesting/",
28
- "./library/skills/ai-characters-in-games/",
29
28
  "./library/skills/ambient-bed/",
30
29
  "./library/skills/animated-loop/",
31
30
  "./library/skills/animation-tree/",
@@ -105,6 +104,7 @@
105
104
  "./library/skills/store-art/",
106
105
  "./library/skills/store-listing/",
107
106
  "./library/skills/summer-analytics/",
107
+ "./library/skills/summer-api/",
108
108
  "./library/skills/summer-apps/",
109
109
  "./library/skills/summer-apps-design/",
110
110
  "./library/skills/summer-assets/",
@@ -115,6 +115,7 @@
115
115
  "./library/skills/summer-matchmaking/",
116
116
  "./library/skills/summer-parties/",
117
117
  "./library/skills/summer-player-data/",
118
+ "./library/skills/summer-router/",
118
119
  "./library/skills/summer-store/",
119
120
  "./library/skills/summer-world-chat/",
120
121
  "./library/skills/summer-world-saves/",
@@ -148,7 +149,7 @@
148
149
  "interface": {
149
150
  "displayName": "Summer Engine",
150
151
  "shortDescription": "Make games with AI",
151
- "longDescription": "Summer Engine is the game engine built for AI. Codex builds a full multiplayer game in it, plays and tests it, and publishes it on summer.games. The plugin adds game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app: project files, scenes, asset import, play mode, screenshots, diagnostics, asset generation and the store. Git, shell, and grep stay with Codex. Session-start orients each session. The pre-commit doctor (opt-in) blocks bad commits.",
152
+ "longDescription": "Summer Engine is the game engine built for AI. Codex builds a full multiplayer game in it, plays and tests it, and publishes it on summer.games. The plugin adds game-dev skills, lifecycle hooks, and a 131-tool MCP bridge to the Summer Engine desktop app: project files, scenes, asset import, play mode, screenshots, diagnostics, asset generation and the store. Git, shell, and grep stay with Codex. Session-start orients each session. The pre-commit doctor (opt-in) blocks bad commits.",
152
153
  "developerName": "Summer Labs, Inc.",
153
154
  "category": "Developer Tools",
154
155
  "capabilities": [
@@ -3,8 +3,8 @@
3
3
  "_generated": "GENERATED from integrations/cursor — do not edit; npm run generate:registry",
4
4
  "name": "summer",
5
5
  "displayName": "Summer Engine",
6
- "version": "4.6.0",
7
- "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
6
+ "version": "4.8.0",
7
+ "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 131-tool MCP bridge to the Summer Engine desktop app.",
8
8
  "author": {
9
9
  "name": "Summer Engine",
10
10
  "email": "founders@summerengine.com"
@@ -38,7 +38,6 @@
38
38
  "./library/skills/3d-lighting/",
39
39
  "./library/skills/adaptive-music/",
40
40
  "./library/skills/agent-playtesting/",
41
- "./library/skills/ai-characters-in-games/",
42
41
  "./library/skills/ambient-bed/",
43
42
  "./library/skills/animated-loop/",
44
43
  "./library/skills/animation-tree/",
@@ -118,6 +117,7 @@
118
117
  "./library/skills/store-art/",
119
118
  "./library/skills/store-listing/",
120
119
  "./library/skills/summer-analytics/",
120
+ "./library/skills/summer-api/",
121
121
  "./library/skills/summer-apps/",
122
122
  "./library/skills/summer-apps-design/",
123
123
  "./library/skills/summer-assets/",
@@ -128,6 +128,7 @@
128
128
  "./library/skills/summer-matchmaking/",
129
129
  "./library/skills/summer-parties/",
130
130
  "./library/skills/summer-player-data/",
131
+ "./library/skills/summer-router/",
131
132
  "./library/skills/summer-store/",
132
133
  "./library/skills/summer-world-chat/",
133
134
  "./library/skills/summer-world-saves/",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "_generated": "GENERATED from integrations/factory — do not edit; npm run generate:registry",
3
3
  "name": "summer",
4
- "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 125-tool MCP bridge to the Summer Engine desktop app.",
5
- "version": "4.6.0",
4
+ "description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. Game-dev skills, lifecycle hooks, and a 131-tool MCP bridge to the Summer Engine desktop app.",
5
+ "version": "4.8.0",
6
6
  "author": {
7
7
  "name": "Summer Engine",
8
8
  "email": "founders@summerengine.com",
@@ -22,7 +22,6 @@
22
22
  "./library/skills/3d-lighting/",
23
23
  "./library/skills/adaptive-music/",
24
24
  "./library/skills/agent-playtesting/",
25
- "./library/skills/ai-characters-in-games/",
26
25
  "./library/skills/ambient-bed/",
27
26
  "./library/skills/animated-loop/",
28
27
  "./library/skills/animation-tree/",
@@ -102,6 +101,7 @@
102
101
  "./library/skills/store-art/",
103
102
  "./library/skills/store-listing/",
104
103
  "./library/skills/summer-analytics/",
104
+ "./library/skills/summer-api/",
105
105
  "./library/skills/summer-apps/",
106
106
  "./library/skills/summer-apps-design/",
107
107
  "./library/skills/summer-assets/",
@@ -112,6 +112,7 @@
112
112
  "./library/skills/summer-matchmaking/",
113
113
  "./library/skills/summer-parties/",
114
114
  "./library/skills/summer-player-data/",
115
+ "./library/skills/summer-router/",
115
116
  "./library/skills/summer-store/",
116
117
  "./library/skills/summer-world-chat/",
117
118
  "./library/skills/summer-world-saves/",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@ All notable changes to summer-engine will be documented here. Following [Keep a
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [4.8.0] - 2026-10-24
8
+
9
+ The Summer API with the Summer sign-in (no static API keys), Studio inside the Summer app, and agents that show the person what they work on. 4.7.0 and the 2026-10-22 4.8.0 draft were never published; this 4.8.0 includes them (sections below).
10
+
11
+ - **`engine.view_set` (`summer_view_set`) shows the person what the agent works on**: the Summer app's view (home, chat, 3D, 2D, script, game, Studio), the right rail, the focus and the nodes to frame, through the engine's `SetView` operation. Needs Summer Engine 4.6 or newer.
12
+ - **`studio.radio`**: agents read and play Summer Radio in the person's open Studio tab (state, play, stop, station, track, next, previous, volume). The operations catalog is synced from PublicSummerEngine main (262 operations).
13
+ - **A generated file never saves into Summer's own folders.** `out` on the generate operations, `assets.get` and `jobs.wait` refuses a game's `.summer/local` (checkpoints, project id) and `~/.summer` (sign-in, runtime), with a warning; nothing is downloaded. The game's memory beside it (`.summer/GameSoul.md`, `.summer/memory/`) is unchanged.
14
+ - **`projects.open_local` switches the running editor.** When Summer Engine already has another project open, that editor now opens the asked one in the same window (the engine's `SwitchProject` operation) instead of answering "another project is open". The MCP says "Opening <game>…" at once (MCP progress) and waits up to 10 s for the new editor to register, then connects to it. An editor without the operation keeps the old answer and names the open project. Other tools never switch the open project.
15
+ - **Signed out? Cloud tools answer with a sign-in link.** When an MCP tool needs the Summer sign-in (`not_signed_in`) or the store approval (`store_login_required`), its answer now ends with a link the agent shows to the person. The MCP starts the same browser sign-in `summer login` uses (`cli_session`) without opening anything, waits for it in the background, and saves the token to `~/.summer`, so the next call works with no restart or reinstall. Engine tools keep working signed out. One link per process until that sign-in finishes or expires (15 minutes).
16
+ - **`summer api` and the `summer_api_*` MCP tools use the Summer sign-in on this computer**: the same Supabase OAuth 2.1 login (PKCE, a loopback redirect on 127.0.0.1) as the store tools. It stores the refresh token in `~/.summer/oauth-token` and refreshes by itself. Each app spends the person's paid credits up to the limit they set in Connected apps.
17
+ - **`summer api run --batch jobs.jsonl`** queues up to 500 Summer API jobs at once (`POST /v1/batches`), one job per line (`model`, `input`, optional `idempotencyKey`), all or none: when paid credits or the app's limit cannot cover them all, nothing runs and the error says how many fit. `--wait` follows every job, `--download [dir]` saves their files, and the JSON summary prints when piped. The MCP tool `summer_api_batch` does the same. No rate limits on paid usage: jobs past the account's running slots answer `queued` and start in order.
18
+ - **`summer api` on Windows.** `--input "@input.json"` reads the input from a file (Windows PowerShell 5 strips the quotes inside `'{"prompt":"..."}'`, and cmd.exe has no single quotes). Batch and input files may be UTF-16 (what Windows PowerShell 5 writes with `>`) or UTF-8 with a BOM, with CRLF lines. Saved files never get a name Windows keeps for a device (`CON`, `NUL`, `COM1`...) or a `:` that would write an NTFS stream. Writing the sign-in to `~/.summer` retries for a moment while antivirus or another Summer process has the file open, and `out` refuses `~/.summer` in any letter case on Windows. CI runs the Summer API and sign-in tests on Windows, macOS and Linux.
19
+ - **`summer token`** prints a one-hour access token for curl: `curl -H "Authorization: Bearer $(summer token)" https://www.summerengine.com/v1/models`.
20
+ - **Studio page ops run in the Summer app's own Studio tab.** `summer_call` sends `studio.map`, `studio.page`, `studio.use_page`, `studio.read_form` and `studio.submit_form` to the running Summer Engine (`POST /api/studio`) when it lists them in `capabilities.studioPage`, with the same input and answers and no store sign-in. When the app has no Studio tab (`no_tab`) or is not running, they go to Summer's server as before.
21
+ - The operations catalog is synced from PublicSummerEngine main: `account.usage` (plan, share used and refill day, in words) and asset generations in `jobs.list`.
22
+ - **Studio pages open in the Summer app's Studio tab.** `studio.open` (`summer_open`, `summer open`) with a `/studio...` page sends the running Summer Engine `Navigate {target: "studio", path}` when the engine lists the `studio` destination, so the app's own Studio tab moves instead of a browser opening. With the engine off or an older engine, the browser opens as before.
23
+ - **Skills `summer-api` and `summer-router`.** `summer-api`: sign in, models, estimate, one job or a batch, follow and download, money refusals (`no_credits`, `over_limit`), prompts per asset type. `summer-router`: AI characters in games on Summer Games (early preview): client and server calls, the character document, tools, speech and images, caps and error tags. `ai-characters-in-games` is retired (its id points to `summer-router`).
24
+ - **`summer router <action>`** runs the Summer Router operations from a shell: `save-character`, `test-character`, `models`, `settings`, `set-settings`, `usage` and the rest. The operations catalog is synced from PublicSummerEngine main.
25
+ - `summer_api_run`, `summer_api_batch` and `summer_api_job` name `no_credits` and `over_limit`, and say that Summer API jobs run to the end once accepted.
26
+ - **Removed:** `summer login --api-key`, the MCP tool `summer_api_key_login` (operation `account.api_key_login`), `SUMMER_API_KEY` and `~/.summer/api-key`.
27
+
28
+ ## 4.8.0 draft - 2026-10-22 (not published)
29
+
30
+ The Summer API from a shell and from MCP. 4.7.0 was not published to npm; 4.8.0 is the first release with it (the [4.7.0] section below).
31
+
32
+ - **`summer api models|estimate|run|jobs`** call the Summer API (`/v1`) with the Summer API key on this computer (`summer login --api-key`, or `SUMMER_API_KEY`). `models` lists the curated game-dev models with their input and USD price (one model by id, `--capability` to filter); `estimate <model> --input '<json>'` prices one exact job and gives a quote id; `run <model> --input '<json>'` spends the person's credits within the key's limit, with one idempotency key per run, `--quote`, `--wait` to follow the job and `--download [dir]` to save its files; `jobs <id>` reads, follows (`--wait`), downloads or `--cancel`s a job. JSON when stdout is not a terminal (or `--json`), a short summary otherwise.
33
+ - **MCP tools** `summer_api_models`, `summer_api_estimate`, `summer_api_run` and `summer_api_job` do the same (operations `models.list`, `models.estimate`, `models.run`, `models.job`, local), in the assets and full profiles.
34
+ - **Until Summer opens the API**, `summer api` and `summer login --api-key` print one line, "Summer API is not open yet.", and exit 1.
35
+ - The operations catalog is synced from PublicSummerEngine main.
36
+
37
+ ## [4.7.0] - 2026-10-22
38
+
39
+ A Summer API key for your agent with one Allow click, and Node 22.
40
+
41
+ - **A Summer API key for your agent with one Allow click.** `summer login --api-key` (and the MCP tool `summer_api_key_login`, operation `account.api_key_login`) asks Summer for a key: the person opens the Summer page it names, checks the code matches and presses Allow, choosing the spend limit there. The key is saved to `~/.summer/api-key` (owner-only, like `auth-token`) and is never printed or returned to the agent; `SUMMER_API_KEY` wins over the file. Without a terminal, `summer login --api-key` prints JSON lines (`needs_approval` with `approve_url` and `user_code`, then `approved`, `denied`, `expired` or `failed`). `summer logout` removes the key too. Until Summer turns API keys on, the request answers `keys_unavailable`.
42
+ - **Node 22+** (`engines.node >= 22`, was 20). Node 20 reached end of life on 2026-04-30 and gets no security fixes. `summer doctor` now fails on Node below 22, and CI runs the CLI on Node 22.23.4 (the 2026-10-21 security release) on macOS, Windows and Ubuntu.
43
+
7
44
  ## [4.6.0] - 2026-10-21
8
45
 
9
46
  A first game that looks finished, and `summer setup` for the Summer desktop app's one-click agent connect.
package/README.md CHANGED
@@ -20,10 +20,10 @@
20
20
 
21
21
  ## Install
22
22
 
23
- - **Start a game now** (Node 20+): `npx -y summer-engine@latest dev`. It asks before it installs the Summer Engine app (about 660 MB, free) when it is missing, creates a starter project here (in `./my-game` when this folder has other files) and opens it, ready for your agent. No sign-in needed to build.
23
+ - **Start a game now** (Node 22+): `npx -y summer-engine@latest dev`. It asks before it installs the Summer Engine app (about 660 MB, free) when it is missing, creates a starter project here (in `./my-game` when this folder has other files) and opens it, ready for your agent. No sign-in needed to build.
24
24
  - **Chat apps** (Claude, ChatGPT, Cursor...): add `https://mcp.summerengine.com/mcp`. Nothing to install.
25
25
  - **Homebrew:** `brew install summerengine/tap/summer`
26
- - **npm** (Node 20+): `npm install -g summer-engine`
26
+ - **npm** (Node 22+): `npm install -g summer-engine`
27
27
 
28
28
  **CLI only** (summer-engine 4.2.0 or newer), no MCP server: `summer setup --cli`. `summer ops search "publish my game"` finds any operation, signed in or not.
29
29
 
@@ -40,7 +40,7 @@
40
40
  | Devin Desktop (formerly Windsurf) | add `"summer-cloud": {"serverUrl": "https://mcp.summerengine.com/mcp"}` under `mcpServers` in `~/.config/devin/mcp_config.json` (Windows `%APPDATA%\devin\mcp_config.json`) |
41
41
  | Claude Desktop, claude.ai | Settings > Connectors > Add custom connector, then paste the URL |
42
42
 
43
- **Engine work: `npx -y summer-engine@latest setup <agent> --yes`** (needs Node 20+). It writes the local server (`summer mcp`) into your agent's config with the skills. The local server drives the Summer Engine app on your machine: build scenes, write scripts, play the game, take screenshots, read errors. After `summer login` it also runs every cloud operation, so your agent needs one server. Or paste the prompt in [Get started](#get-started-one-prompt) and your agent does every step.
43
+ **Engine work: `npx -y summer-engine@latest setup <agent> --yes`** (needs Node 22+). It writes the local server (`summer mcp`) into your agent's config with the skills. The local server drives the Summer Engine app on your machine: build scenes, write scripts, play the game, take screenshots, read errors. After `summer login` it also runs every cloud operation, so your agent needs one server. Or paste the prompt in [Get started](#get-started-one-prompt) and your agent does every step.
44
44
 
45
45
  **CLI only (summer-engine 4.2.0 or newer): `npx -y summer-engine@latest setup <agent> --cli --yes`.** No MCP server: your agent runs Summer as commands in its shell. Setup installs the skills for your agent, with `summer-cli`, the guide to the commands, and writes no MCP entry. Your agent signs you in with `summer login`, finds an operation with `summer ops search "<what you want>"` and runs it as `summer <area> <action> --<field> <value>`, with `--dry-run`, `--wait` and `--json`. Engine commands (`summer engine …`) drive the Summer Engine app on your machine. Each skill ends with a table of the command for every tool it names. Add `--assets-only` for only the asset skills. `--no-mcp` is the same as `--cli`.
46
46
 
package/commands/start.md CHANGED
@@ -17,7 +17,7 @@ $ARGUMENTS
17
17
  ## 1. Look before you ask (read-only, a few seconds)
18
18
 
19
19
  - Tools you already have: the `summer-engine` server (local, drives the Summer Engine desktop app; its tools include `summer_get_project_context`) and the `summer-cloud` server (hosted, Summer's cloud; its tools include `summer_whoami`, `summer_search` and `summer_call` once the user is signed in). A server that needs sign-in shows no tools yet.
20
- - `npx -y summer-engine@latest status` tells you whether the engine is running. If `npx` is missing, there is no Node: plan it first (3a) and have them install Node 20 or later (nodejs.org) for the build.
20
+ - `npx -y summer-engine@latest status` tells you whether the engine is running. If `npx` is missing, there is no Node: plan it first (3a) and have them install Node 22 or later (nodejs.org) for the build.
21
21
 
22
22
  Do not run `summer setup claude-code` here: this plugin already brings the MCP servers and the skills, and a second copy of the skills only crowds the list.
23
23
 
@@ -0,0 +1,43 @@
1
+ import { Command } from "commander";
2
+ import { type SummerApiDependencies } from "../../core/summer-api.js";
3
+ /**
4
+ * `summer api models|estimate|run|jobs`: the Summer API (core/summer-api.ts)
5
+ * from a shell. JSON when stdout is not a terminal (or with --json); a short
6
+ * summary for people. `--wait` follows a job until it finishes; `--download
7
+ * [dir]` also saves its files. `run --batch file.jsonl` queues up to 500
8
+ * jobs at once (all or none). While Summer has not opened the API, every
9
+ * command prints one line, "Summer API is not open yet.", and exits 1.
10
+ */
11
+ export interface ApiCommandIo {
12
+ isTTY: boolean;
13
+ print: (line: string) => void;
14
+ printError: (line: string) => void;
15
+ }
16
+ type JsonOption = {
17
+ json?: boolean;
18
+ };
19
+ /** A text file as any system writes it: UTF-8 with or without a BOM, or UTF-16 (Windows PowerShell 5's > and Out-File). */
20
+ export declare function readTextFile(file: string): Promise<string>;
21
+ /**
22
+ * --input: a JSON object, or "@file" with one. Windows PowerShell 5 strips the
23
+ * inner quotes of '{"prompt":"..."}' before the CLI sees them, and cmd.exe has
24
+ * no single quotes, so a file is the dependable way there (quoted: PowerShell
25
+ * reads a bare @ as splatting).
26
+ */
27
+ export declare function parseInput(text: string | undefined): Promise<Record<string, unknown>>;
28
+ /** Run one api command: its answer as JSON or as the summary, its errors as one line (or one JSON object). */
29
+ export declare function runApiAction(opts: JsonOption, work: () => Promise<{
30
+ data: unknown;
31
+ summary: string[];
32
+ }>, io?: ApiCommandIo): Promise<number>;
33
+ /** run --batch: queue every line of the file as one batch, then --wait and --download as for one job. */
34
+ export declare function runBatch(file: string, opts: {
35
+ idempotencyKey?: string;
36
+ wait?: boolean;
37
+ download?: string | boolean;
38
+ }, overrides?: Partial<SummerApiDependencies>): Promise<{
39
+ data: unknown;
40
+ summary: string[];
41
+ }>;
42
+ export declare const apiCommand: Command;
43
+ export {};
@@ -0,0 +1,191 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { Command } from "commander";
3
+ import { API_NOT_OPEN, SummerApiError, cancelJob, createBatch, downloadBatchFiles, downloadJobFiles, estimateJob, getJob, isFinished, listModels, isBatchFinished, parseBatchLines, runJob, waitForBatch, waitForJob, } from "../../core/summer-api.js";
4
+ const defaultIo = () => ({
5
+ isTTY: process.stdout.isTTY === true,
6
+ print: (line) => console.log(line),
7
+ printError: (line) => console.error(line),
8
+ });
9
+ /** A text file as any system writes it: UTF-8 with or without a BOM, or UTF-16 (Windows PowerShell 5's > and Out-File). */
10
+ export async function readTextFile(file) {
11
+ const bytes = await readFile(file);
12
+ if (bytes[0] === 0xff && bytes[1] === 0xfe)
13
+ return bytes.subarray(2).toString("utf16le");
14
+ return bytes.toString("utf8").replace(/^\uFEFF/, "");
15
+ }
16
+ /**
17
+ * --input: a JSON object, or "@file" with one. Windows PowerShell 5 strips the
18
+ * inner quotes of '{"prompt":"..."}' before the CLI sees them, and cmd.exe has
19
+ * no single quotes, so a file is the dependable way there (quoted: PowerShell
20
+ * reads a bare @ as splatting).
21
+ */
22
+ export async function parseInput(text) {
23
+ if (!text)
24
+ return {};
25
+ if (text.startsWith("@")) {
26
+ try {
27
+ text = await readTextFile(text.slice(1));
28
+ }
29
+ catch {
30
+ throw new SummerApiError("invalid_input", `Cannot read the input file ${text.slice(1)}.`, 400);
31
+ }
32
+ }
33
+ let value;
34
+ try {
35
+ value = JSON.parse(text);
36
+ }
37
+ catch {
38
+ throw new SummerApiError("invalid_input", "--input must be a JSON object, e.g. --input '{\"prompt\":\"a wooden chest\"}', or a file with one: --input \"@input.json\" (in Windows PowerShell and cmd).", 400);
39
+ }
40
+ if (!value || typeof value !== "object" || Array.isArray(value))
41
+ throw new SummerApiError("invalid_input", "--input must be a JSON object.", 400);
42
+ return value;
43
+ }
44
+ /** Run one api command: its answer as JSON or as the summary, its errors as one line (or one JSON object). */
45
+ export async function runApiAction(opts, work, io = defaultIo()) {
46
+ const json = opts.json === true || !io.isTTY;
47
+ try {
48
+ const { data, summary } = await work();
49
+ if (json)
50
+ io.print(JSON.stringify(data, null, 2));
51
+ else
52
+ for (const line of summary)
53
+ io.print(line);
54
+ return 0;
55
+ }
56
+ catch (error) {
57
+ if (error instanceof SummerApiError && error.code === "api_not_open") {
58
+ io.printError(API_NOT_OPEN);
59
+ return 1;
60
+ }
61
+ const code = error instanceof SummerApiError ? error.code : "request_failed";
62
+ const message = error instanceof Error ? error.message : String(error);
63
+ // A refused batch's details say how many jobs fit and how much more is needed.
64
+ const details = error instanceof SummerApiError && error.body?.details && typeof error.body.details === "object" ? error.body.details : undefined;
65
+ if (json)
66
+ io.print(JSON.stringify({ ok: false, error: { code, message, ...(details ? { details } : {}) } }));
67
+ else
68
+ io.printError(message);
69
+ return 1;
70
+ }
71
+ }
72
+ function jobSummary(job, files = []) {
73
+ const lines = [`Job ${job.id}: ${job.state}${typeof job.price === "string" ? ` (${job.price} USD)` : typeof job.estimatedPrice === "string" ? ` (estimated ${job.estimatedPrice} USD)` : ""}`];
74
+ const result = (job.result && typeof job.result === "object" ? job.result : {});
75
+ for (const asset of (Array.isArray(result.assets) ? result.assets : [])) {
76
+ lines.push(` ${asset.assetId ?? "asset"}: ${asset.fileUrl ?? ""}`);
77
+ }
78
+ for (const file of files)
79
+ lines.push(` saved ${file}`);
80
+ if (!isFinished(job))
81
+ lines.push(` Follow it: summer api jobs ${job.id} --wait`);
82
+ return lines;
83
+ }
84
+ /** --wait and --download for a job that came back: follow it, then save its files. */
85
+ async function finish(job, opts) {
86
+ const follow = opts.wait === true || opts.download !== undefined;
87
+ const final = follow && !isFinished(job) ? await waitForJob(job) : job;
88
+ const dir = typeof opts.download === "string" ? opts.download : ".";
89
+ const files = opts.download !== undefined && final.state === "done" ? await downloadJobFiles(final, dir) : [];
90
+ return { data: { job: final, ...(opts.download !== undefined ? { files } : {}) }, summary: jobSummary(final, files) };
91
+ }
92
+ function batchSummary(batch, files = {}) {
93
+ const counts = Object.entries(batch.counts ?? {}).map(([state, count]) => `${count} ${state}`).join(", ");
94
+ const lines = [`Batch ${batch.id}: ${batch.jobs.length} jobs${typeof batch.estimatedPrice === "string" ? ` (estimated ${batch.estimatedPrice} USD)` : ""}${counts ? `, ${counts}` : ""}`];
95
+ for (const job of batch.jobs) {
96
+ lines.push(` ${job.id}: ${job.state}${typeof job.model === "string" ? ` ${job.model}` : ""}`);
97
+ for (const file of files[job.id] ?? [])
98
+ lines.push(` saved ${file}`);
99
+ }
100
+ if (!isBatchFinished(batch))
101
+ lines.push(` Follow a job: summer api jobs <id> --wait`);
102
+ return lines;
103
+ }
104
+ /** run --batch: queue every line of the file as one batch, then --wait and --download as for one job. */
105
+ export async function runBatch(file, opts, overrides = {}) {
106
+ let text;
107
+ try {
108
+ text = await readTextFile(file);
109
+ }
110
+ catch {
111
+ throw new SummerApiError("invalid_input", `Cannot read the batch file ${file}.`, 400);
112
+ }
113
+ const batch = await createBatch(parseBatchLines(text), opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}, overrides);
114
+ const follow = opts.wait === true || opts.download !== undefined;
115
+ const final = follow && !isBatchFinished(batch) ? await waitForBatch(batch, {}, overrides) : batch;
116
+ const files = opts.download !== undefined ? await downloadBatchFiles(final, typeof opts.download === "string" ? opts.download : ".", overrides) : undefined;
117
+ return { data: { batch: final, ...(files ? { files } : {}) }, summary: batchSummary(final, files) };
118
+ }
119
+ const exit = (code) => {
120
+ if (code)
121
+ process.exitCode = code;
122
+ };
123
+ const modelsCommand = new Command("models")
124
+ .description("The Summer API's curated game-dev models, with input and price; one model with its id (no sign-in needed)")
125
+ .argument("[model]", "A model id or alias, e.g. 3d/default")
126
+ .option("--capability <capability>", "Only one kind: image, 3d, video, voice, dialogue, sound or music")
127
+ .option("--json", "JSON (the default when stdout is not a terminal)")
128
+ .action(async (model, opts) => exit(await runApiAction(opts, async () => {
129
+ const data = await listModels({ ...(model ? { model } : {}), ...(opts.capability ? { capability: opts.capability } : {}) });
130
+ const models = (Array.isArray(data.models) ? data.models : data.model ? [data.model] : []);
131
+ const price = (entry) => {
132
+ const p = (entry.price && typeof entry.price === "object" ? entry.price : {});
133
+ return typeof p.price === "string" ? `${p.price} USD${typeof p.unit === "string" ? ` per ${p.unit}` : ""}` : "";
134
+ };
135
+ return { data, summary: models.map((entry) => `${String(entry.id).padEnd(28)} ${String(entry.capability ?? "").padEnd(9)} ${price(entry)}`) };
136
+ })));
137
+ const estimateCommand = new Command("estimate")
138
+ .description("The exact price of one job before it runs, and a quote id that holds it for 15 minutes; charges nothing")
139
+ .argument("<model>", "A model id or alias, e.g. 3d/default")
140
+ .option("--input <json>", "The model's input as a JSON object, or @file.json (summer api models <model> shows its schema)")
141
+ .option("--json", "JSON (the default when stdout is not a terminal)")
142
+ .action(async (model, opts) => exit(await runApiAction(opts, async () => {
143
+ const data = await estimateJob(model, await parseInput(opts.input));
144
+ return { data, summary: [`${data.model ?? model}: ${data.atMost ? "at most " : ""}${data.price} ${data.currency ?? "USD"}${data.quoteId ? ` quote ${data.quoteId}` : ""}`] };
145
+ })));
146
+ const runSubcommand = new Command("run")
147
+ .description("Run one job into your Summer assets, or a batch of up to 500 with --batch; spends your paid credits within this app's limit (Connected apps)")
148
+ .argument("[model]", "A model id or alias, e.g. 3d/default (not with --batch)")
149
+ .option("--input <json>", "The model's input as a JSON object, or @file.json")
150
+ .option("--batch <file>", "A JSONL file, one job per line: {\"model\": ..., \"input\": {...}, \"idempotencyKey\": optional}; up to 500, all or none")
151
+ .option("--quote <quoteId>", "From summer api estimate: refuse to charge more than it quoted")
152
+ .option("--idempotency-key <key>", "Reuse to retry the same run or batch without running it twice (default: a new one)")
153
+ .option("--wait", "Follow the job (or every job of the batch) until it finishes")
154
+ .option("--download [dir]", "Follow it and save its files (default: this folder)")
155
+ .option("--json", "JSON (the default when stdout is not a terminal)")
156
+ .action(async (model, opts) => exit(await runApiAction(opts, async () => {
157
+ if (opts.batch) {
158
+ if (model || opts.input || opts.quote)
159
+ throw new SummerApiError("invalid_input", "--batch takes the model and input from each line of its file; leave out <model>, --input and --quote.", 400);
160
+ return runBatch(opts.batch, opts);
161
+ }
162
+ if (!model)
163
+ throw new SummerApiError("invalid_input", "Name a model (summer api run 3d/default --input '{...}'), or pass --batch file.jsonl.", 400);
164
+ const follow = opts.wait === true || opts.download !== undefined;
165
+ const job = await runJob(model, await parseInput(opts.input), {
166
+ ...(opts.quote ? { quoteId: opts.quote } : {}),
167
+ ...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
168
+ ...(follow ? { waitSeconds: 60 } : {}),
169
+ });
170
+ return finish(job, opts);
171
+ })));
172
+ const jobsCommand = new Command("jobs")
173
+ .description("Read a job; --wait follows it, --download saves its files")
174
+ .argument("<jobId>", "The job id summer api run answered")
175
+ .option("--wait", "Follow the job until it finishes")
176
+ .option("--download [dir]", "Follow it and save its files (default: this folder)")
177
+ .option("--cancel", "Ask to stop the job (Summer API jobs, queued or running, run to the end: the answer is unsupported)")
178
+ .option("--json", "JSON (the default when stdout is not a terminal)")
179
+ .action(async (jobId, opts) => exit(await runApiAction(opts, async () => {
180
+ if (opts.cancel) {
181
+ const job = await cancelJob(jobId);
182
+ return { data: { job }, summary: jobSummary(job) };
183
+ }
184
+ return finish(await getJob(jobId), opts);
185
+ })));
186
+ export const apiCommand = new Command("api")
187
+ .description("The Summer API: game-dev models, prices, estimates and jobs, with this computer's Summer sign-in (summer login)")
188
+ .addCommand(modelsCommand)
189
+ .addCommand(estimateCommand)
190
+ .addCommand(runSubcommand)
191
+ .addCommand(jobsCommand);
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
+ import { type CliSessionPollDependencies } from "../../core/cli-session.js";
2
3
  export declare const loginCommand: Command;
3
4
  /** How long `summer login` waits for the store's Allow click after the account sign-in. */
4
5
  export declare const STORE_STEP_TIMEOUT_MS: number;
@@ -27,12 +28,8 @@ export declare function runLoginCommand(opts: {
27
28
  store?: boolean;
28
29
  force?: boolean;
29
30
  }, overrides?: Partial<LoginCommandDependencies>): Promise<void>;
30
- export interface LoginDependencies {
31
- fetch: typeof fetch;
31
+ export interface LoginDependencies extends CliSessionPollDependencies {
32
32
  openUrl: (url: string) => Promise<unknown>;
33
33
  randomId: () => string;
34
- sleep: (ms: number) => Promise<void>;
35
- now: () => number;
36
- log: (message: string) => void;
37
34
  }
38
35
  export declare function runLogin(overrides?: Partial<LoginDependencies>): Promise<void>;
@@ -1,21 +1,13 @@
1
1
  import { Command } from "commander";
2
2
  import { randomUUID } from "crypto";
3
3
  import open from "open";
4
- import { getAuthToken, saveLoginSession, } from "../../core/auth.js";
5
- import { summerRequestHeaders } from "../../core/agent-host.js";
4
+ import { getAuthToken } from "../../core/auth.js";
5
+ import { cliSessionLoginUrl, pollCliSession } from "../../core/cli-session.js";
6
6
  import { resolveGatewayUrl } from "../../core/config.js";
7
- import { readStoredOAuthToken, runStoreLogin } from "../../core/oauth.js";
8
- const POLL_INTERVAL_MS = 2000;
9
- // One generous window on ONE session id. First-time users may need to create an
10
- // account and confirm an email before they can approve the CLI. The gateway
11
- // never expires a pending session (the completed payload waits under this id
12
- // for pickup), so the worst mistake would be rotating ids mid-wait — the user
13
- // would approve the original browser tab while we poll a different session.
14
- const POLL_TIMEOUT_MS = 900000;
15
- const HEARTBEAT_MS = 30000;
7
+ import { runStoreLogin, storeSignInWorks } from "../../core/oauth.js";
16
8
  export const loginCommand = new Command("login")
17
- .description("Sign in to Summer in your browser: your Summer account, then the store, publishing and Grow tools in the same browser")
18
- .option("--store", "Only the store sign-in (uploads with store.upload_file, item sync with economy.sync_items, the store, publishing, board and Grow tools); plain summer login does it too")
9
+ .description("Sign in to Summer in your browser: your Summer account, then the Summer sign-in (OAuth) the Summer API, store, publishing and Grow tools use, in the same browser")
10
+ .option("--store", "Only the Summer sign-in (OAuth): the Summer API (summer api, summer token), uploads with store.upload_file, item sync, the store, publishing, board and Grow tools; plain summer login does it too")
19
11
  .option("--no-store", "Only the Summer account sign-in; skip the store sign-in")
20
12
  .option("--force", "Force re-authentication even if already logged in")
21
13
  .action(async (opts) => {
@@ -25,7 +17,8 @@ export const loginCommand = new Command("login")
25
17
  export const STORE_STEP_TIMEOUT_MS = 3 * 60_000;
26
18
  const defaultCommandDependencies = {
27
19
  hasAuthToken: async () => Boolean(await getAuthToken()),
28
- hasStoreSignIn: async () => Boolean(await readStoredOAuthToken()),
20
+ // Checked with the sign-in server: a stored token for a revoked app is not a sign-in.
21
+ hasStoreSignIn: () => storeSignInWorks(),
29
22
  login: () => runLogin(),
30
23
  storeLogin: ({ newClient, timeoutMs }) => runStoreLogin({ fetch, now: Date.now, openUrl: open, log: console.log, newClient, ...(timeoutMs ? { timeoutMs } : {}) }),
31
24
  log: console.log,
@@ -79,9 +72,6 @@ export async function runLoginCommand(opts, overrides = {}) {
79
72
  deps.log('Your Summer login is saved. To add the store, publishing and Grow tools later, run "summer login --store".');
80
73
  }
81
74
  }
82
- /** The gateway answered with a terminal 4xx — retrying the same URL cannot help. */
83
- class LoginRejectedError extends Error {
84
- }
85
75
  const defaultDependencies = {
86
76
  fetch,
87
77
  openUrl: open,
@@ -94,7 +84,7 @@ export async function runLogin(overrides = {}) {
94
84
  const deps = { ...defaultDependencies, ...overrides };
95
85
  const gatewayUrl = await resolveGatewayUrl();
96
86
  const sessionId = deps.randomId();
97
- const loginUrl = `${gatewayUrl}/login?cli_session=${encodeURIComponent(sessionId)}`;
87
+ const loginUrl = cliSessionLoginUrl(gatewayUrl, sessionId);
98
88
  deps.log("Sign in at: " + loginUrl);
99
89
  deps.log("");
100
90
  try {
@@ -105,67 +95,6 @@ export async function runLogin(overrides = {}) {
105
95
  deps.log("");
106
96
  }
107
97
  deps.log("Waiting for authentication...");
108
- const pollUrl = `${gatewayUrl}/api/auth/cli-login?session=${encodeURIComponent(sessionId)}`;
109
- const startTime = deps.now();
110
- let lastHeartbeat = startTime;
111
- let lastError = null;
112
- while (deps.now() - startTime < POLL_TIMEOUT_MS) {
113
- await deps.sleep(POLL_INTERVAL_MS);
114
- if (deps.now() - lastHeartbeat >= HEARTBEAT_MS) {
115
- lastHeartbeat = deps.now();
116
- deps.log('Still waiting — finish signing in (or creating your account) in the browser, then click "Yes, Sign In". Same link: ' +
117
- loginUrl +
118
- (lastError ? ` (last error: ${lastError})` : ""));
119
- }
120
- // Only the network round-trip is retried. A completed session is handled
121
- // OUTSIDE this try: saveLoginSession rejecting the token (mismatched sub,
122
- // non-CLI token type, already expired) is terminal — the gateway will
123
- // return the same payload forever, so polling on would only bury the
124
- // recovery message under a 15-minute timeout.
125
- let completed = null;
126
- try {
127
- const res = await deps.fetch(pollUrl, {
128
- headers: summerRequestHeaders({ surface: "cli" }),
129
- signal: AbortSignal.timeout(5000),
130
- });
131
- if (!res.ok) {
132
- if (res.status === 503) {
133
- lastError =
134
- "The login service is unavailable (Redis/auth is not configured).";
135
- }
136
- else if (res.status >= 500) {
137
- lastError = `The login service failed (${res.status}).`;
138
- }
139
- else if (res.status === 429 || res.status === 408) {
140
- lastError = `The login service asked us to slow down (${res.status}).`;
141
- }
142
- else {
143
- // 401/403/404 and the other 4xx are terminal: this URL will not
144
- // start succeeding on its own.
145
- throw new LoginRejectedError(`The login request was rejected (${res.status}). Recovery: check that gateway.url (${gatewayUrl}) is the Summer gateway you meant, then run "summer login" again.`);
146
- }
147
- continue;
148
- }
149
- const data = (await res.json());
150
- if (data.status === "pending")
151
- continue;
152
- if (data.status === "complete" && data.token) {
153
- completed = { token: data.token, user: data.user, scopes: data.scopes };
154
- }
155
- else {
156
- lastError = "The login service returned an incomplete authentication result.";
157
- }
158
- }
159
- catch (err) {
160
- if (err instanceof LoginRejectedError)
161
- throw err;
162
- lastError = err instanceof Error ? err.message : "Network error";
163
- }
164
- if (completed) {
165
- await saveLoginSession(completed);
166
- deps.log(`\nLogged in as ${completed.user?.email || "unknown"}`);
167
- return;
168
- }
169
- }
170
- throw new Error(`Login timed out after ${Math.round(POLL_TIMEOUT_MS / 60000)} minutes.${lastError ? ` Last error: ${lastError}` : ""} Recovery: run "summer login" again and complete "Yes, Sign In" in the browser tab it opens.`);
98
+ const completed = await pollCliSession(gatewayUrl, sessionId, deps);
99
+ deps.log(`\nLogged in as ${completed.user?.email || "unknown"}`);
171
100
  }