summer-engine 4.5.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +5 -4
- package/.cursor-plugin/plugin.json +4 -3
- package/.factory-plugin/plugin.json +4 -3
- package/CHANGELOG.md +50 -0
- package/README.md +3 -3
- package/commands/start.md +1 -1
- package/dist/cli/commands/api.d.ts +43 -0
- package/dist/cli/commands/api.js +191 -0
- package/dist/cli/commands/login.d.ts +2 -5
- package/dist/cli/commands/login.js +10 -81
- package/dist/cli/commands/setup.js +175 -32
- package/dist/cli/commands/token.d.ts +17 -0
- package/dist/cli/commands/token.js +25 -0
- package/dist/cli/index.js +4 -0
- package/dist/core/api-client.d.ts +5 -1
- package/dist/core/api-client.js +21 -0
- package/dist/core/capabilities/doctor.d.ts +1 -1
- package/dist/core/capabilities/doctor.js +1 -1
- package/dist/core/capabilities/game-task-plan.d.ts +2 -2
- package/dist/core/capabilities/navigation/open.d.ts +3 -0
- package/dist/core/capabilities/navigation/open.js +53 -0
- package/dist/core/capabilities/projects-local.js +2 -1
- package/dist/core/capabilities/runtime-control.d.ts +2 -2
- package/dist/core/capabilities/seeing/args.d.ts +4 -4
- package/dist/core/capabilities/tool-dispatch.js +37 -0
- package/dist/core/capabilities/ui-control.d.ts +2 -2
- package/dist/core/capabilities/view-set.d.ts +11 -0
- package/dist/core/capabilities/view-set.js +15 -0
- package/dist/core/capability-skew.d.ts +8 -0
- package/dist/core/capability-skew.js +18 -0
- package/dist/core/cli-session.d.ts +25 -0
- package/dist/core/cli-session.js +97 -0
- package/dist/core/engine-self-start.d.ts +20 -0
- package/dist/core/engine-self-start.js +108 -5
- package/dist/core/oauth.d.ts +15 -2
- package/dist/core/oauth.js +26 -3
- package/dist/core/ops/areas.d.ts +1 -1
- package/dist/core/ops/areas.js +5 -3
- package/dist/core/ops/engine-answer.js +5 -2
- package/dist/core/ops/engine.js +6 -0
- package/dist/core/ops/save-files.d.ts +2 -0
- package/dist/core/ops/save-files.js +13 -0
- package/dist/core/ops/studio-page.d.ts +22 -0
- package/dist/core/ops/studio-page.js +63 -0
- package/dist/core/sign-in-link.d.ts +38 -0
- package/dist/core/sign-in-link.js +106 -0
- package/dist/core/store.js +19 -1
- package/dist/core/summer-api.d.ts +114 -0
- package/dist/core/summer-api.js +281 -0
- package/dist/installer/agent-config.d.ts +29 -0
- package/dist/installer/agent-config.js +75 -11
- package/dist/installer/agent-detect.d.ts +44 -0
- package/dist/installer/agent-detect.js +166 -0
- package/dist/installer/agent-table.d.ts +19 -0
- package/dist/installer/agent-table.js +130 -0
- package/dist/installer/setup-error.d.ts +19 -0
- package/dist/installer/setup-error.js +34 -0
- package/dist/installer/version-check.d.ts +5 -0
- package/dist/installer/version-check.js +21 -0
- package/dist/mcp/ops-surface.d.ts +10 -0
- package/dist/mcp/ops-surface.js +18 -1
- package/dist/mcp/server.js +16 -4
- package/dist/mcp/tool-results.d.ts +9 -0
- package/dist/mcp/tool-results.js +64 -0
- package/dist/mcp/tool-surface.js +7 -1
- package/dist/mcp/tools/api-tools.d.ts +50 -0
- package/dist/mcp/tools/api-tools.js +65 -0
- package/dist/mcp/tools/local-project-tools.d.ts +14 -0
- package/dist/mcp/tools/local-project-tools.js +13 -2
- package/dist/mcp/tools/navigation-tools.js +1 -0
- package/dist/mcp/tools/scene-tools.js +7 -0
- package/dist/project-memory/agent-instructions.js +11 -3
- package/gemini-extension.json +3 -2
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +15 -2
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/skills/README.md +6 -5
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/design-npc/resource.yaml +2 -1
- package/library/skills/make-game/SKILL.md +4 -4
- package/library/skills/navigate-summer/SKILL.md +23 -2
- package/library/skills/navigate-summer/resource.yaml +5 -3
- package/library/skills/summer-api/SKILL.md +198 -0
- package/library/skills/summer-api/resource.yaml +38 -0
- package/library/skills/summer-assets/SKILL.md +1 -1
- package/library/skills/summer-assets/resource.yaml +3 -2
- package/library/skills/summer-router/SKILL.md +251 -0
- package/library/skills/summer-router/resource.yaml +35 -0
- package/library/tools/api-batch/resource.yaml +82 -0
- package/library/tools/api-estimate/resource.yaml +52 -0
- package/library/tools/api-job/resource.yaml +58 -0
- package/library/tools/api-models/resource.yaml +57 -0
- package/library/tools/api-run/resource.yaml +70 -0
- package/library/tools/view-set/GUIDE.md +3 -0
- package/library/tools/view-set/resource.yaml +78 -0
- package/package.json +3 -3
- package/registry/generated/aliases.json +7 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -2
- package/registry/generated/index.json +317 -52
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -3
- package/registry/generated/plugin.claude.json +3 -2
- package/registry/generated/plugin.codex.json +5 -4
- package/registry/generated/plugin.cursor.json +4 -3
- package/registry/generated/plugin.factory.json +4 -3
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +6 -5
- package/registry/generated/skills-registry.json +29 -15
- package/registry/operations/legacy-names.json +30 -0
- package/registry/operations/public-catalog.json +9169 -4926
- package/library/skills/ai-characters-in-games/SKILL.md +0 -235
- package/library/skills/ai-characters-in-games/resource.yaml +0 -30
|
@@ -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
|
|
5
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
109
|
-
|
|
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
|
}
|
|
@@ -2,8 +2,10 @@ import { spawnSync } from "child_process";
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { relative } from "path";
|
|
4
4
|
import { Command, Option } from "commander";
|
|
5
|
-
import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
|
|
5
|
+
import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveBundledRuntime, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
|
|
6
6
|
import { previewNote, setupSkills } from "../../installer/setup.js";
|
|
7
|
+
import { detectAgents } from "../../installer/agent-detect.js";
|
|
8
|
+
import { SetupError, toStepError } from "../../installer/setup-error.js";
|
|
7
9
|
import { printDoctorResult, runDoctor } from "../../core/capabilities/doctor.js";
|
|
8
10
|
import { brandLine, c, sym, tildeify } from "../../core/format.js";
|
|
9
11
|
import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
|
|
@@ -23,6 +25,8 @@ const MCP_ENTRY_FLAGS = [
|
|
|
23
25
|
["localDev", "--local-dev"],
|
|
24
26
|
["channel", "--channel"],
|
|
25
27
|
["profile", "--profile"],
|
|
28
|
+
["runtime", "--runtime"],
|
|
29
|
+
["packageDir", "--package-dir"],
|
|
26
30
|
];
|
|
27
31
|
export const setupCommand = new Command("setup")
|
|
28
32
|
.description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games). --cli installs the skills only: the agent runs summer commands in its shell, with no MCP server.")
|
|
@@ -32,6 +36,9 @@ export const setupCommand = new Command("setup")
|
|
|
32
36
|
.option("--dry-run", "Show planned changes without writing files")
|
|
33
37
|
.option("--print", "Print the MCP config snippet instead of writing files")
|
|
34
38
|
.option("--local-dev", "Point the agent at this checkout's built CLI (node <repo>/dist/bin/summer.js mcp) instead of npx summer-engine@latest — for testing unpublished builds. SUMMER_DEV=1 does the same.")
|
|
39
|
+
.option("--runtime <node>", "Run the MCP server with this node executable instead of npx: <node> <package-dir>/dist/bin/summer.js mcp. Needs --package-dir. For installers that ship their own Node.js")
|
|
40
|
+
.option("--package-dir <dir>", "The summer-engine package folder (package.json and dist/bin/summer.js) that --runtime runs")
|
|
41
|
+
.option("--detect", "List every supported agent: installed or not, its MCP config file, and whether it already lists summer-engine. Writes nothing; takes no agent. Use with --json")
|
|
35
42
|
.option("--channel <dist-tag>", "npm dist-tag or exact version the agent's MCP entry runs: npx -y summer-engine@<dist-tag> mcp (default latest; a CLI newer than npm latest pins next when next is this version, else this version). SUMMER_CHANNEL does the same.")
|
|
36
43
|
.option("--yes", "Apply practical setup steps without prompting")
|
|
37
44
|
.option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games)")
|
|
@@ -49,8 +56,52 @@ export const setupCommand = new Command("setup")
|
|
|
49
56
|
// Pre-reversal opt-in, kept one release as a hidden no-op so old scripts still parse.
|
|
50
57
|
.addOption(new Option("--include-preview", "No-op: preview skills install by default").hideHelp())
|
|
51
58
|
.action(async (agentArg, opts) => {
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
if (opts.detect) {
|
|
60
|
+
runDetect(agentArg, opts);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!opts.json) {
|
|
64
|
+
await setupAgent(agentArg, opts);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// --json: one JSON document on stdout, also when setup cannot start.
|
|
68
|
+
try {
|
|
69
|
+
await setupAgent(agentArg, opts);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.log(JSON.stringify({ ok: false, error: toStepError(error, "setup_failed"), steps: [] }, null, 2));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Runs one step. With --json a throw becomes the step's coded error and the
|
|
78
|
+
* other steps still run; without it the throw ends setup as it always did.
|
|
79
|
+
*/
|
|
80
|
+
async function runStep(json, fallbackCode, run) {
|
|
81
|
+
if (!json)
|
|
82
|
+
return { value: await run() };
|
|
83
|
+
try {
|
|
84
|
+
return { value: await run() };
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return { value: null, error: toStepError(error, fallbackCode) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function setupAgent(agentArg, opts) {
|
|
91
|
+
let agent;
|
|
92
|
+
try {
|
|
93
|
+
agent = resolveAgentSelection(agentArg, opts.agent);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw new SetupError("unknown_agent", error instanceof Error ? error.message : String(error));
|
|
97
|
+
}
|
|
98
|
+
let scope;
|
|
99
|
+
try {
|
|
100
|
+
scope = resolveConfigScope(opts.scope);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new SetupError("invalid_scope", error instanceof Error ? error.message : String(error));
|
|
104
|
+
}
|
|
54
105
|
if (opts.removeAllowRules) {
|
|
55
106
|
await removeCliAllowRules(agent, scope, opts);
|
|
56
107
|
return;
|
|
@@ -63,8 +114,14 @@ export const setupCommand = new Command("setup")
|
|
|
63
114
|
if (opts.allowRules)
|
|
64
115
|
console.error(` ${sym.warn()} ${c.yellow("--allow-rules works with --cli only (the MCP server asks through the agent's own tool approvals); no rules were added.")}`);
|
|
65
116
|
const profile = resolveSetupProfile(opts);
|
|
66
|
-
const
|
|
67
|
-
|
|
117
|
+
const runtime = resolveBundledRuntime(opts.runtime, opts.packageDir);
|
|
118
|
+
if (runtime && (opts.localDev || opts.channel)) {
|
|
119
|
+
throw new SetupError("invalid_option", `--runtime runs a bundled summer-engine; leave out ${opts.localDev ? "--local-dev" : "--channel"}.`);
|
|
120
|
+
}
|
|
121
|
+
const json = Boolean(opts.json);
|
|
122
|
+
// A bundled runtime runs the package beside it, so npm's tags do not matter (and no network call is made).
|
|
123
|
+
const defaultChannel = runtime ? { channel: "latest" } : await resolveDefaultChannel(cliVersion);
|
|
124
|
+
const mcp = await runStep(json, "mcp_entry_failed", () => configureAgentMcp({
|
|
68
125
|
agent,
|
|
69
126
|
scope,
|
|
70
127
|
dryRun: opts.dryRun,
|
|
@@ -72,14 +129,19 @@ export const setupCommand = new Command("setup")
|
|
|
72
129
|
localDev: Boolean(opts.localDev) || process.env.SUMMER_DEV === "1",
|
|
73
130
|
channel: opts.channel ?? process.env.SUMMER_CHANNEL ?? defaultChannel.channel,
|
|
74
131
|
profile,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
132
|
+
runtime: runtime ?? undefined,
|
|
133
|
+
}));
|
|
134
|
+
const config = mcp.value;
|
|
135
|
+
const gamesSkipped = opts.games === false || opts.assetsOnly || opts.print;
|
|
136
|
+
const gamesStep = gamesSkipped
|
|
137
|
+
? { value: null }
|
|
138
|
+
: await runStep(json, "summer_games_failed", () => configureSummerGamesMcp({ agent, scope, dryRun: opts.dryRun }));
|
|
139
|
+
const games = gamesStep.value;
|
|
140
|
+
if (!runtime && !opts.channel && !process.env.SUMMER_CHANNEL && defaultChannel.note && !opts.print) {
|
|
141
|
+
// On stderr with --json, so stdout stays one JSON document.
|
|
142
|
+
(json ? console.error : console.log)(` ${c.dim(defaultChannel.note)}`);
|
|
143
|
+
}
|
|
144
|
+
const skillsStep = await runStep(json, "skills_install_failed", () => setupSkills(agent, {
|
|
83
145
|
dryRun: Boolean(opts.dryRun || opts.print),
|
|
84
146
|
yes: Boolean(opts.yes),
|
|
85
147
|
force: Boolean(opts.force),
|
|
@@ -87,26 +149,104 @@ export const setupCommand = new Command("setup")
|
|
|
87
149
|
recommended: Boolean(opts.recommended),
|
|
88
150
|
stableOnly: Boolean(opts.stableOnly),
|
|
89
151
|
assetsOnly: Boolean(opts.assetsOnly),
|
|
90
|
-
});
|
|
91
|
-
const
|
|
152
|
+
}));
|
|
153
|
+
const skills = skillsStep.value ?? { status: "failed", message: skillsStep.error?.message ?? "Skill install failed." };
|
|
154
|
+
const projectStep = await runStep(json, "project_files_failed", () => setupProjectInstructions(opts));
|
|
155
|
+
const projectFiles = projectStep.value;
|
|
92
156
|
const doctor = await runDoctor({ quiet: true });
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
if (json) {
|
|
158
|
+
const steps = [
|
|
159
|
+
mcpEntryStep(config, mcp.error),
|
|
160
|
+
summerGamesStep(agent, games, gamesSkipped ? "skipped" : null, gamesStep.error, Boolean(opts.dryRun)),
|
|
161
|
+
skillsStepResult(skills, skillsStep.error),
|
|
162
|
+
projectFilesStep(projectFiles, projectStep.error, opts),
|
|
163
|
+
doctorStep(doctor),
|
|
164
|
+
];
|
|
165
|
+
const ok = steps.every((step) => step.ok);
|
|
166
|
+
console.log(JSON.stringify({ ok, agent, scope, steps, config, games, skills, projectFiles, doctor }, null, 2));
|
|
167
|
+
if (!ok)
|
|
168
|
+
process.exit(1);
|
|
169
|
+
return;
|
|
105
170
|
}
|
|
171
|
+
printSetupResult(config, games, skills, doctor, projectFiles);
|
|
106
172
|
if (!doctor.ok || skills.status === "failed") {
|
|
107
173
|
process.exit(1);
|
|
108
174
|
}
|
|
109
|
-
}
|
|
175
|
+
}
|
|
176
|
+
function mcpEntryStep(config, error) {
|
|
177
|
+
if (!config)
|
|
178
|
+
return { step: "mcp_entry", ok: false, status: "failed", error: error ?? { code: "mcp_entry_failed", message: "The MCP entry was not written." } };
|
|
179
|
+
const status = config.print ? "printed" : config.dryRun ? (config.changed ? "planned" : "unchanged") : config.wrote ? "written" : "unchanged";
|
|
180
|
+
return { step: "mcp_entry", ok: true, status, path: config.path, ...(config.warnings.length ? { warnings: config.warnings } : {}) };
|
|
181
|
+
}
|
|
182
|
+
function summerGamesStep(agent, games, skipped, error, dryRun) {
|
|
183
|
+
if (error)
|
|
184
|
+
return { step: "summer_games", ok: false, status: "failed", error };
|
|
185
|
+
if (skipped)
|
|
186
|
+
return { step: "summer_games", ok: true, status: "skipped" };
|
|
187
|
+
// configureSummerGamesMcp returns null only for agents without a Summer Games entry shape.
|
|
188
|
+
if (!games)
|
|
189
|
+
return { step: "summer_games", ok: true, status: "not_supported", warnings: [`${agentLabel(agent)} gets no Summer Games entry from setup.`] };
|
|
190
|
+
const status = games.existingName ? "exists" : dryRun ? (games.changed ? "planned" : "unchanged") : games.wrote ? "written" : "unchanged";
|
|
191
|
+
return { step: "summer_games", ok: true, status, path: games.path };
|
|
192
|
+
}
|
|
193
|
+
function skillsStepResult(skills, error) {
|
|
194
|
+
if (skills.status === "failed") {
|
|
195
|
+
return {
|
|
196
|
+
step: "skills",
|
|
197
|
+
ok: false,
|
|
198
|
+
status: "failed",
|
|
199
|
+
error: error ?? { code: "skills_install_failed", message: [skills.message, skills.stderr].filter(Boolean).join(" ") },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { step: "skills", ok: true, status: skills.status, ...(skills.destination ? { path: skills.destination } : {}) };
|
|
203
|
+
}
|
|
204
|
+
function projectFilesStep(project, error, opts) {
|
|
205
|
+
if (error)
|
|
206
|
+
return { step: "project_files", ok: false, status: "failed", error };
|
|
207
|
+
if (!project) {
|
|
208
|
+
const skipped = opts.projectFiles === false || opts.print || opts.assetsOnly;
|
|
209
|
+
return { step: "project_files", ok: true, status: skipped ? "skipped" : "not_in_project" };
|
|
210
|
+
}
|
|
211
|
+
// A file setup leaves alone (a link out of the project, a broken Summer block) is a warning, as in the human output.
|
|
212
|
+
const warnings = project.files.filter((file) => file.reason).map((file) => `${file.path}: ${file.reason}`);
|
|
213
|
+
return { step: "project_files", ok: true, status: project.written ? "written" : "planned", path: project.root, ...(warnings.length ? { warnings } : {}) };
|
|
214
|
+
}
|
|
215
|
+
function doctorStep(doctor) {
|
|
216
|
+
if (doctor.ok)
|
|
217
|
+
return { step: "doctor", ok: true, status: "passed" };
|
|
218
|
+
const failed = doctor.checks.filter((check) => check.status === "fail");
|
|
219
|
+
return {
|
|
220
|
+
step: "doctor",
|
|
221
|
+
ok: false,
|
|
222
|
+
status: "failed",
|
|
223
|
+
error: { code: "doctor_failed", message: failed.map((check) => `${check.label}: ${check.message}`).join("; ") || "Doctor found a problem." },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** `summer setup --detect`: every agent, installed or not, and whether it is linked. Writes nothing. */
|
|
227
|
+
function runDetect(agentArg, opts) {
|
|
228
|
+
if (agentArg || opts.agent) {
|
|
229
|
+
const message = "--detect lists every agent; leave out the agent name.";
|
|
230
|
+
if (!opts.json)
|
|
231
|
+
throw new Error(message);
|
|
232
|
+
console.log(JSON.stringify({ ok: false, error: { code: "invalid_option", message }, agents: [] }, null, 2));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const agents = detectAgents();
|
|
237
|
+
if (opts.json) {
|
|
238
|
+
console.log(JSON.stringify({ ok: true, os: process.platform, agents }, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.log("");
|
|
242
|
+
for (const agent of agents) {
|
|
243
|
+
if (agent.status === "legacy" && !agent.installed)
|
|
244
|
+
continue;
|
|
245
|
+
const mark = agent.installed ? sym.ok() : c.dim("-");
|
|
246
|
+
const linked = agent.mcpEntry ? c.dim(`linked ${tildeify(agent.configPath)}`) : agent.installed ? c.dim(`not linked: summer setup ${agent.id}`) : c.dim("not found");
|
|
247
|
+
console.log(` ${mark} ${agent.label} ${linked}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
110
250
|
/**
|
|
111
251
|
* `summer setup <agent> --cli`: the skills (the asset skills with --assets-only)
|
|
112
252
|
* and no MCP server, so the agent runs Summer as `summer <area> <action>` in its
|
|
@@ -117,7 +257,7 @@ async function setupCliOnly(agent, scope, opts) {
|
|
|
117
257
|
const spec = agentSpec(agent);
|
|
118
258
|
const mcpFlags = MCP_ENTRY_FLAGS.filter(([key]) => opts[key] !== undefined && opts[key] !== false).map(([, flag]) => flag);
|
|
119
259
|
if (mcpFlags.length > 0) {
|
|
120
|
-
throw new
|
|
260
|
+
throw new SetupError("invalid_option", `${mcpFlags.join(", ")} sets up the MCP entry, and --cli writes none. Leave ${mcpFlags.length === 1 ? "it" : "them"} out, or leave out --cli.`);
|
|
121
261
|
}
|
|
122
262
|
const refusal = cliSetupRefusal(spec);
|
|
123
263
|
if (refusal)
|
|
@@ -278,11 +418,11 @@ async function recordedMcpEntry(agent, scope) {
|
|
|
278
418
|
function resolveSetupProfile(opts) {
|
|
279
419
|
const profile = opts.profile === undefined ? undefined : parseProfile(opts.profile);
|
|
280
420
|
if (profile === null) {
|
|
281
|
-
throw new
|
|
421
|
+
throw new SetupError("invalid_option", `Unknown --profile "${opts.profile}". Use one of: ${Object.keys(PROFILES).join(", ")}.`);
|
|
282
422
|
}
|
|
283
423
|
if (opts.assetsOnly) {
|
|
284
424
|
if (profile && profile !== "assets")
|
|
285
|
-
throw new
|
|
425
|
+
throw new SetupError("invalid_option", "--assets-only uses the assets profile; leave out --profile.");
|
|
286
426
|
return "assets";
|
|
287
427
|
}
|
|
288
428
|
return profile;
|
|
@@ -305,7 +445,10 @@ function printSetupResult(config, games, skills, doctor, projectFiles) {
|
|
|
305
445
|
else {
|
|
306
446
|
console.log(` ${sym.ok()} Already linked to ${c.bold(label)} ${c.dim(tildeify(config.path))}`);
|
|
307
447
|
}
|
|
308
|
-
if (config.
|
|
448
|
+
if (config.runtime) {
|
|
449
|
+
console.log(` ${c.dim("(bundled runtime)")} MCP server command: ${c.dim(`${config.server.command} ${config.server.args.join(" ")}`)}`);
|
|
450
|
+
}
|
|
451
|
+
else if (config.localDev) {
|
|
309
452
|
console.log(` ${c.dim("(local dev)")} MCP server command: ${c.dim(`${config.server.command} ${config.server.args.join(" ")}`)}`);
|
|
310
453
|
}
|
|
311
454
|
else if (config.channel !== "latest") {
|
|
@@ -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
|
package/dist/core/api-client.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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" | "
|
|
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" | "
|
|
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[];
|