summer-engine 4.0.0 → 4.3.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 +2 -2
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +15 -11
- package/.cursor-plugin/plugin.json +5 -3
- package/.factory-plugin/plugin.json +3 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +48 -0
- package/README.md +21 -10
- package/dist/cli/command-usage.d.ts +8 -0
- package/dist/cli/command-usage.js +22 -0
- package/dist/cli/commands/config.js +3 -2
- package/dist/cli/commands/create.js +12 -3
- package/dist/cli/commands/list.d.ts +5 -0
- package/dist/cli/commands/list.js +30 -13
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/memory.js +6 -2
- package/dist/cli/commands/ops.d.ts +20 -3
- package/dist/cli/commands/ops.js +60 -27
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +219 -16
- package/dist/cli/commands/skills.js +20 -7
- package/dist/cli/commands/status.d.ts +26 -0
- package/dist/cli/commands/status.js +36 -21
- package/dist/cli/commands/tool.js +4 -0
- package/dist/cli/index.js +2 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +8 -2
- package/dist/core/capabilities/apps.d.ts +10 -10
- package/dist/core/capabilities/audit/args.d.ts +6 -6
- package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
- package/dist/core/capabilities/game-task-plan.d.ts +2 -2
- package/dist/core/capabilities/hdri-import.d.ts +2 -2
- package/dist/core/capabilities/motion-generation.d.ts +2 -2
- package/dist/core/capabilities/placement.d.ts +23 -23
- package/dist/core/capabilities/runtime-control.d.ts +38 -38
- package/dist/core/capabilities/seeing/args.d.ts +16 -16
- package/dist/core/capabilities/ui-control.d.ts +2 -2
- package/dist/core/capabilities/world-snapshot.d.ts +4 -4
- package/dist/core/cli-usage-beacon.d.ts +1 -0
- package/dist/core/cli-usage-beacon.js +18 -0
- package/dist/core/cli-usage.d.ts +49 -0
- package/dist/core/cli-usage.js +108 -0
- package/dist/core/feedback/client.d.ts +1 -1
- package/dist/core/feedback/client.js +3 -2
- package/dist/core/hosted-mcp.d.ts +7 -1
- package/dist/core/hosted-mcp.js +6 -4
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/ops/areas.d.ts +11 -0
- package/dist/core/ops/areas.js +24 -0
- package/dist/core/ops/catalog.d.ts +11 -0
- package/dist/core/ops/catalog.js +24 -1
- package/dist/core/ops/cloud.d.ts +10 -2
- package/dist/core/ops/cloud.js +51 -28
- package/dist/core/ops/contract.d.ts +2 -1
- package/dist/core/ops/contract.js +2 -10
- package/dist/core/ops/public-catalog.d.ts +48 -0
- package/dist/core/ops/public-catalog.js +56 -0
- package/dist/core/ops/search.d.ts +0 -1
- package/dist/core/ops/search.js +169 -37
- package/dist/core/telemetry.d.ts +10 -0
- package/dist/core/telemetry.js +58 -29
- package/dist/installer/agent-config.js +5 -0
- package/dist/installer/agent-table.d.ts +9 -0
- package/dist/installer/agent-table.js +23 -2
- package/dist/installer/cli-permissions.d.ts +65 -0
- package/dist/installer/cli-permissions.js +182 -0
- package/dist/installer/setup.d.ts +2 -0
- package/dist/installer/setup.js +3 -0
- package/dist/installer/skills-refresh.d.ts +29 -0
- package/dist/installer/skills-refresh.js +261 -0
- package/dist/installer/version-check.d.ts +22 -2
- package/dist/installer/version-check.js +66 -9
- package/dist/mcp/ops-surface.js +16 -6
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +38 -8
- package/dist/project-memory/agent-instructions.d.ts +47 -0
- package/dist/project-memory/agent-instructions.js +226 -0
- package/gemini-extension.json +3 -2
- package/integrations/README.md +1 -1
- package/integrations/claude/README.md +6 -0
- package/integrations/claude/manifest-target.json +4 -0
- package/integrations/gemini/README.md +11 -0
- package/integrations/windsurf/README.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +16 -0
- package/library/skills/README.md +5 -4
- package/library/skills/adaptive-music/SKILL.md +21 -0
- package/library/skills/agent-playtesting/SKILL.md +22 -0
- package/library/skills/ai-characters-in-games/SKILL.md +15 -0
- package/library/skills/ambient-bed/SKILL.md +16 -0
- package/library/skills/animated-loop/SKILL.md +19 -0
- package/library/skills/animation-tree/SKILL.md +26 -0
- package/library/skills/art-direction/SKILL.md +14 -0
- package/library/skills/asset-strategy/SKILL.md +30 -0
- package/library/skills/audio-direction/SKILL.md +16 -0
- package/library/skills/auto-fire-targeting/SKILL.md +14 -0
- package/library/skills/brainstorm-game/SKILL.md +13 -0
- package/library/skills/browse-templates/SKILL.md +10 -0
- package/library/skills/camera-rigs/SKILL.md +20 -0
- package/library/skills/character-animation-wiring/SKILL.md +24 -0
- package/library/skills/character-model/SKILL.md +28 -0
- package/library/skills/character-movement/SKILL.md +24 -0
- package/library/skills/character-portrait/SKILL.md +15 -0
- package/library/skills/cinematic-cutscene/SKILL.md +21 -0
- package/library/skills/combat-basics/SKILL.md +19 -0
- package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
- package/library/skills/concept-art/SKILL.md +13 -0
- package/library/skills/concept-to-level/SKILL.md +20 -1
- package/library/skills/concept-to-level/resource.yaml +2 -2
- package/library/skills/create-asset-sheet/SKILL.md +18 -0
- package/library/skills/debug/SKILL.md +32 -0
- package/library/skills/debugging-game-feel/SKILL.md +12 -0
- package/library/skills/design-level/SKILL.md +17 -0
- package/library/skills/design-mechanic/SKILL.md +17 -0
- package/library/skills/design-npc/SKILL.md +21 -0
- package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
- package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
- package/library/skills/environment-kit/SKILL.md +23 -0
- package/library/skills/export-and-ship/SKILL.md +23 -0
- package/library/skills/fabricating-assets/SKILL.md +23 -0
- package/library/skills/facial-and-lipsync/SKILL.md +22 -0
- package/library/skills/fps-controller/SKILL.md +17 -0
- package/library/skills/game-feel/SKILL.md +26 -0
- package/library/skills/gameskill/SKILL.md +11 -0
- package/library/skills/gdscript-patterns/SKILL.md +15 -0
- package/library/skills/generate-motion/SKILL.md +21 -0
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
- package/library/skills/grow-analytics/SKILL.md +10 -0
- package/library/skills/headless-scripting/SKILL.md +13 -0
- package/library/skills/in-game-purchases/SKILL.md +11 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
- package/library/skills/make-game/SKILL.md +29 -0
- package/library/skills/multiplayer/SKILL.md +12 -0
- package/library/skills/multiplayer-movement/SKILL.md +18 -0
- package/library/skills/multiplayer-project/SKILL.md +17 -0
- package/library/skills/multiplayer-publish/SKILL.md +17 -5
- package/library/skills/multiplayer-state/SKILL.md +16 -0
- package/library/skills/multiplayer-testing/SKILL.md +15 -0
- package/library/skills/music-track/SKILL.md +16 -0
- package/library/skills/navigate-summer/SKILL.md +15 -0
- package/library/skills/new-project/SKILL.md +11 -0
- package/library/skills/organic-model/SKILL.md +24 -0
- package/library/skills/pixel-art/SKILL.md +15 -0
- package/library/skills/play/SKILL.md +18 -0
- package/library/skills/playtesting-a-feature/SKILL.md +23 -0
- package/library/skills/procedural-animation/SKILL.md +18 -0
- package/library/skills/prop-model/SKILL.md +23 -0
- package/library/skills/publish-your-game/SKILL.md +27 -0
- package/library/skills/release-readiness/SKILL.md +12 -0
- package/library/skills/remote-deploy/SKILL.md +14 -0
- package/library/skills/retarget/SKILL.md +20 -0
- package/library/skills/running-in-the-cloud/SKILL.md +10 -0
- package/library/skills/save-load/SKILL.md +15 -0
- package/library/skills/scene-composition/SKILL.md +17 -0
- package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
- package/library/skills/scene-scripting/SKILL.md +30 -0
- package/library/skills/scene-to-level/SKILL.md +22 -0
- package/library/skills/skill-test/SKILL.md +10 -0
- package/library/skills/skybox-panorama/SKILL.md +20 -0
- package/library/skills/sound-effect/SKILL.md +23 -0
- package/library/skills/spatial-placement/SKILL.md +30 -0
- package/library/skills/sprite-sheet/SKILL.md +23 -0
- package/library/skills/store-art/SKILL.md +11 -0
- package/library/skills/store-listing/SKILL.md +10 -0
- package/library/skills/summer-analytics/SKILL.md +15 -0
- package/library/skills/summer-apps/SKILL.md +29 -0
- package/library/skills/summer-assets/SKILL.md +31 -1
- package/library/skills/summer-assets/resource.yaml +2 -2
- package/library/skills/summer-cli/SKILL.md +133 -0
- package/library/skills/summer-cli/resource.yaml +31 -0
- package/library/skills/summer-friends/SKILL.md +14 -0
- package/library/skills/summer-leaderboards/SKILL.md +16 -0
- package/library/skills/summer-match-results/SKILL.md +17 -0
- package/library/skills/summer-matchmaking/SKILL.md +17 -0
- package/library/skills/summer-parties/SKILL.md +14 -0
- package/library/skills/summer-player-data/SKILL.md +14 -0
- package/library/skills/summer-store/SKILL.md +15 -0
- package/library/skills/summer-world-chat/SKILL.md +14 -0
- package/library/skills/summer-world-saves/SKILL.md +14 -0
- package/library/skills/text-to-motion/SKILL.md +15 -0
- package/library/skills/tileable-texture/SKILL.md +18 -0
- package/library/skills/trailer-shot/SKILL.md +18 -0
- package/library/skills/tune-performance/SKILL.md +26 -0
- package/library/skills/ui-basics/SKILL.md +14 -0
- package/library/skills/ui-graphics/SKILL.md +17 -0
- package/library/skills/use-widget-asset/SKILL.md +14 -0
- package/library/skills/using-summer/SKILL.md +26 -0
- package/library/skills/using-summer/resource.yaml +2 -1
- package/library/skills/vehicle-model/SKILL.md +27 -0
- package/library/skills/verifying-scenes/SKILL.md +29 -0
- package/library/skills/vfx-dissolve/SKILL.md +19 -0
- package/library/skills/vfx-fire/SKILL.md +26 -0
- package/library/skills/vfx-hit-spark/SKILL.md +23 -0
- package/library/skills/vfx-lightning/SKILL.md +23 -0
- package/library/skills/vfx-magic-glow/SKILL.md +23 -0
- package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
- package/library/skills/vfx-smoke/SKILL.md +26 -0
- package/library/skills/vfx-water-ripple/SKILL.md +23 -0
- package/library/skills/voice-line/SKILL.md +16 -0
- package/library/skills/web-and-touch-input/SKILL.md +19 -0
- package/library/skills/web-compatibility-look/SKILL.md +20 -0
- package/library/skills/world-building-3d/SKILL.md +28 -0
- package/package.json +3 -2
- package/registry/generated/counts.json +2 -2
- package/registry/generated/gemini-extension.json +3 -2
- package/registry/generated/index.json +157 -118
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude-directory.json +168 -0
- package/registry/generated/plugin.claude.json +3 -2
- package/registry/generated/plugin.codex.json +15 -11
- package/registry/generated/plugin.cursor.json +5 -3
- package/registry/generated/plugin.factory.json +3 -2
- package/registry/generated/server.json +11 -2
- package/registry/generated/skills-index.md +5 -4
- package/registry/generated/skills-registry.json +16 -2
- package/registry/operations/legacy-names.json +35 -0
- package/registry/operations/public-catalog.json +33160 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { parse as parseYaml } from "yaml";
|
|
16
|
-
import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
|
|
16
|
+
import { agentSpec, allAgentSpecs, cliSetupRefusal, defaultPathContext, resolveSkillPath } from "./agent-table.js";
|
|
17
17
|
import { join, sep } from "node:path";
|
|
18
18
|
import { PACKAGE_ROOT } from "../core/package-root.js";
|
|
19
19
|
import { configureAgentMcp, parseAgent, SUMMER_MCP_SERVER_NAME, topLevelKey, } from "./agent-config.js";
|
|
@@ -219,11 +219,28 @@ export function readSkillMarker(dir) {
|
|
|
219
219
|
return null;
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
|
|
222
|
+
/** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
|
|
223
|
+
export function readKeptSkills(dir) {
|
|
224
|
+
try {
|
|
225
|
+
const kept = JSON.parse(readFileSync(join(dir, SKILL_VERSION_MARKER_FILENAME), "utf-8")).kept;
|
|
226
|
+
return Array.isArray(kept) ? kept.filter((name) => typeof name === "string") : [];
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Write the folder's marker. The skills an upgrade refresh left stay listed
|
|
234
|
+
* (so later refreshes keep leaving them), except the ones this install just
|
|
235
|
+
* wrote: `installed` names them; leave it out to keep every one.
|
|
236
|
+
*/
|
|
237
|
+
export function writeSkillMarker(dir, version, now = new Date(), installed) {
|
|
223
238
|
const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
|
|
239
|
+
const kept = readKeptSkills(dir).filter((name) => !installed?.includes(name));
|
|
224
240
|
const payload = {
|
|
225
241
|
version,
|
|
226
242
|
installedAt: now.toISOString(),
|
|
243
|
+
...(kept.length > 0 ? { kept } : {}),
|
|
227
244
|
};
|
|
228
245
|
writeFileSync(path, JSON.stringify(payload, null, 2) + "\n", "utf-8");
|
|
229
246
|
return path;
|
|
@@ -378,9 +395,16 @@ async function resolveRecordedInstall(resolver, agent) {
|
|
|
378
395
|
* The command that refreshes an agent's skills without changing how its MCP
|
|
379
396
|
* entry was recorded: the local-dev form re-links the same checkout
|
|
380
397
|
* (`setup --local-dev --force` re-copies the checkout's skills); the npx form
|
|
381
|
-
* is for installs that already run the published package.
|
|
398
|
+
* is for installs that already run the published package. An agent with no
|
|
399
|
+
* summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
|
|
382
400
|
*/
|
|
383
401
|
export function skillsRefreshCommand(agent, install, channel = "latest") {
|
|
402
|
+
if (install?.cliOnly) {
|
|
403
|
+
// No MCP entry to keep: --local-dev and --channel shape only that entry.
|
|
404
|
+
return install.localDev && install.cliPath
|
|
405
|
+
? `node ${install.cliPath} setup ${agent} --cli --yes --force`
|
|
406
|
+
: `npx clear-npx-cache && npx -y summer-engine@${channel} setup ${agent} --cli --yes --force`;
|
|
407
|
+
}
|
|
384
408
|
if (install?.localDev && install.cliPath) {
|
|
385
409
|
return `node ${install.cliPath} setup ${agent} --local-dev --yes --force`;
|
|
386
410
|
}
|
|
@@ -473,6 +497,37 @@ export function readRecordedMcpServer(text, format) {
|
|
|
473
497
|
}
|
|
474
498
|
return null;
|
|
475
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Does the agent's config list a summer-engine server in any shape: the stdio
|
|
502
|
+
* entry setup writes, a URL entry under that name, or (Claude Code) a
|
|
503
|
+
* per-project entry under projects.<dir>.mcpServers?
|
|
504
|
+
*/
|
|
505
|
+
export function listsSummerEngine(text, format) {
|
|
506
|
+
if (readRecordedMcpServer(text, format))
|
|
507
|
+
return true;
|
|
508
|
+
if (!text.trim())
|
|
509
|
+
return false;
|
|
510
|
+
if (format === "toml")
|
|
511
|
+
return new RegExp(`^\\s*\\[mcp_servers\\.(?:"${SUMMER_MCP_SERVER_NAME}"|${SUMMER_MCP_SERVER_NAME})[\\].]`, "m").test(text);
|
|
512
|
+
if (format === "toml-array")
|
|
513
|
+
return new RegExp(`^\\s*name\\s*=\\s*"${SUMMER_MCP_SERVER_NAME}"`, "m").test(text);
|
|
514
|
+
let parsed;
|
|
515
|
+
try {
|
|
516
|
+
parsed = format === "yaml-goose" || format === "yaml-hermes" ? parseYaml(text) : JSON.parse(text);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
if (!parsed || typeof parsed !== "object")
|
|
522
|
+
return false;
|
|
523
|
+
const root = parsed;
|
|
524
|
+
const projects = root.projects && typeof root.projects === "object" ? Object.values(root.projects) : [];
|
|
525
|
+
const maps = [
|
|
526
|
+
...[topLevelKey(format), "mcpServers", "servers", "mcp", "context_servers", "extensions", "mcp_servers", "amp.mcpServers"].map((key) => root[key]),
|
|
527
|
+
...projects.map((project) => (project && typeof project === "object" ? project.mcpServers : undefined)),
|
|
528
|
+
];
|
|
529
|
+
return maps.some((map) => Boolean(map) && typeof map === "object" && SUMMER_MCP_SERVER_NAME in map);
|
|
530
|
+
}
|
|
476
531
|
/** Is the CLI running this check a source checkout rather than an installed
|
|
477
532
|
* package? npm/npx/global installs all live under a node_modules directory. */
|
|
478
533
|
export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
|
|
@@ -488,16 +543,17 @@ export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
|
|
|
488
543
|
*/
|
|
489
544
|
export async function detectRecordedInstall(agent) {
|
|
490
545
|
const supported = parseAgent(agent);
|
|
546
|
+
let cliOnly = false;
|
|
491
547
|
if (supported) {
|
|
492
548
|
try {
|
|
493
549
|
const target = await configureAgentMcp({ agent: supported, scope: "user", print: true });
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
550
|
+
const text = existsSync(target.path) ? readFileSync(target.path, "utf-8") : "";
|
|
551
|
+
const recorded = readRecordedMcpServer(text, target.format);
|
|
552
|
+
if (recorded) {
|
|
553
|
+
const localDev = isLocalDevServerConfig(recorded);
|
|
554
|
+
return { localDev, cliPath: localDev ? recorded.args[0] ?? null : null, source: "agent-config" };
|
|
500
555
|
}
|
|
556
|
+
cliOnly = !listsSummerEngine(text, target.format) && cliSetupRefusal(agentSpec(supported)) === null;
|
|
501
557
|
}
|
|
502
558
|
catch {
|
|
503
559
|
// Unreadable config: fall through to the running-CLI answer.
|
|
@@ -508,6 +564,7 @@ export async function detectRecordedInstall(agent) {
|
|
|
508
564
|
localDev: checkout,
|
|
509
565
|
cliPath: checkout ? [PACKAGE_ROOT, "dist", "bin", "summer.js"].join(sep) : null,
|
|
510
566
|
source: "running-cli",
|
|
567
|
+
...(cliOnly ? { cliOnly } : {}),
|
|
511
568
|
};
|
|
512
569
|
}
|
|
513
570
|
export function buildBootDriftNotice(installedVersion, registry, agent) {
|
package/dist/mcp/ops-surface.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { localEntries, localInputSchema, localWireForm, missingInput } from "../core/ops/catalog.js";
|
|
4
|
-
import { answerOfHosted, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
|
|
4
|
+
import { answerOfHosted, describeFromShipped, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
|
|
5
5
|
import { errorAnswer, isLocalOp, legacyNames, localToolsFor, mcpNameOf, resolveOpName } from "../core/ops/contract.js";
|
|
6
6
|
import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, renderOpRefs, splitToolArgs } from "../core/ops/engine.js";
|
|
7
7
|
import { answerOfEngineResult, engineFailure, textsOf } from "../core/ops/engine-answer.js";
|
|
@@ -343,12 +343,20 @@ export function applyOpsSurface(server, options) {
|
|
|
343
343
|
limit: z.number().int().min(1).max(20).optional().describe("Most results (default 5)."),
|
|
344
344
|
}, { title: "Find a Summer operation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, (async ({ query, area, limit }) => toolResult(await search(query, area, limit ?? 5))));
|
|
345
345
|
server.tool(SUMMER_CALL, "Run any Summer operation by name (from summer_search) with its input. Engine operations run here; cloud ones run on Summer's server with the same approvals: money, publishing and live changes answer needs_approval with a link. 3.x tool names work too.", {
|
|
346
|
-
op: z.string().min(1).max(80).describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
|
|
346
|
+
op: z.string().min(1).max(80).optional().describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
|
|
347
|
+
// Agents reach for `name` (the field summer_search answers with); it means op.
|
|
348
|
+
name: z.string().min(1).max(80).optional().describe("Same as op."),
|
|
347
349
|
input: z.record(z.unknown()).optional().describe("The operation's input, matching the schema summer_search returned."),
|
|
348
350
|
dryRun: z.boolean().optional().describe("Check and describe the call without doing it."),
|
|
349
351
|
idempotencyKey: z.string().min(1).max(128).optional().describe("Retry key for writes; reuse it only to retry the identical call."),
|
|
350
|
-
}, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, input, dryRun, idempotencyKey }, extra) => {
|
|
351
|
-
const name = op.trim();
|
|
352
|
+
}, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, name: opAlias, input, dryRun, idempotencyKey }, extra) => {
|
|
353
|
+
const name = (op ?? opAlias ?? "").trim();
|
|
354
|
+
if (!name) {
|
|
355
|
+
return toolResult(errorAnswer("invalid_input", "Pass op: the operation name from summer_search, e.g. engine.add_node.", { next: { op: "ops.search" } }));
|
|
356
|
+
}
|
|
357
|
+
if (op !== undefined && opAlias !== undefined && op.trim() !== opAlias.trim()) {
|
|
358
|
+
return toolResult(errorAnswer("invalid_input", `op ("${op}") and name ("${opAlias}") differ; pass one operation name.`, { next: { op: "ops.search" }, details: { path: "name" } }));
|
|
359
|
+
}
|
|
352
360
|
const args = input ?? {};
|
|
353
361
|
// A split operation's own tool name, or a mixed 3.x tool whose action picks the operation.
|
|
354
362
|
const action = typeof args.action === "string" ? args.action : typeof args.mode === "string" ? args.mode : undefined;
|
|
@@ -378,13 +386,15 @@ export function applyOpsSurface(server, options) {
|
|
|
378
386
|
// `out` stays here: this package saves the files (core/ops/save-files.ts).
|
|
379
387
|
const { input: sent, out } = options.files.take(resolved, args);
|
|
380
388
|
let raw;
|
|
389
|
+
// Describing a cloud operation works signed out: the shipped catalog has it.
|
|
390
|
+
const describe = (answer) => (resolved === "ops.describe" && typeof args.name === "string" ? describeFromShipped(args.name, answer) : null) ?? answer;
|
|
381
391
|
try {
|
|
382
392
|
raw = await options.cloud.call(SUMMER_CALL, { op: name, ...(input ? { input: sent } : {}), ...(dryRun ? { dryRun } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) });
|
|
383
393
|
}
|
|
384
394
|
catch (error) {
|
|
385
|
-
return toolResult(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey }));
|
|
395
|
+
return toolResult(describe(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey })));
|
|
386
396
|
}
|
|
387
|
-
return toolResult(await options.files.finish(resolved, answerOfHosted(raw), out));
|
|
397
|
+
return toolResult(describe(await options.files.finish(resolved, answerOfHosted(raw), out)));
|
|
388
398
|
}));
|
|
389
399
|
}
|
|
390
400
|
/** A split operation's input shape: its fields from the 3.x tool's shape, the required ones required. */
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface CreateMcpServerOptions {
|
|
|
38
38
|
cloud?: CloudCaller;
|
|
39
39
|
/** v4 only: list every local 3.x name too (the legacy toolset, legacy-toolset.ts). */
|
|
40
40
|
legacy?: boolean;
|
|
41
|
+
/** One line for this session only, after the instructions (e.g. the installed skills were refreshed). */
|
|
42
|
+
notice?: string | null;
|
|
41
43
|
}
|
|
42
44
|
export declare function createMcpServer(selection?: ToolSelection, options?: CreateMcpServerOptions): {
|
|
43
45
|
server: McpServer;
|
|
@@ -45,6 +47,14 @@ export declare function createMcpServer(selection?: ToolSelection, options?: Cre
|
|
|
45
47
|
/** v4: saves generated files on this machine (the mounted hosted tools share it). */
|
|
46
48
|
files: FileSaver;
|
|
47
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* First v4 start after an upgrade: bring the installed Summer skills to this
|
|
52
|
+
* version before the legacy decision, which stays on while they are 3.x (and
|
|
53
|
+
* whenever the config asks for it). Not on the v3 surface, whose people chose
|
|
54
|
+
* the 3.x tools, and not under summer doctor's boot probe, whose session would
|
|
55
|
+
* swallow the one notice. A fault here never stops the server.
|
|
56
|
+
*/
|
|
57
|
+
export declare function refreshSkillsAfterUpgrade(surface: SurfaceVersion): string | null;
|
|
48
58
|
export interface StartMcpServerOptions {
|
|
49
59
|
projectPath?: string;
|
|
50
60
|
instanceId?: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -31,12 +31,14 @@ import { defaultHostedMountDependencies, prepareHostedMount } from "./hosted-mou
|
|
|
31
31
|
import { applyOpsSurface, hostedFileTools, OPS_MCP_ASSETS_INSTRUCTIONS, OPS_MCP_INSTRUCTIONS, SUMMER_CALL, SUMMER_SEARCH } from "./ops-surface.js";
|
|
32
32
|
import { FileSaver } from "../core/ops/save-files.js";
|
|
33
33
|
import { opForToolCall } from "../core/ops/contract.js";
|
|
34
|
+
import { knownOp } from "../core/ops/catalog.js";
|
|
34
35
|
import { v4HostedUrl } from "../core/ops/cloud.js";
|
|
35
36
|
import { serveProviderSafeToolSchemas } from "./provider-safe-schema.js";
|
|
36
37
|
import { legacyHostedDescription, resolveLegacy, takeLegacyToolset } from "./legacy-toolset.js";
|
|
38
|
+
import { refreshInstalledSkills, refreshNotice } from "../installer/skills-refresh.js";
|
|
37
39
|
import { OUTPUT_CAPS, PROFILE_TOOL_LIMIT, PROFILES, TOOLSETS, annotationsFor, registryAuthorities, resolveToolSelection, selectionIncludes, withMaxCharactersArg, } from "./tool-surface.js";
|
|
38
40
|
import { shapeToolResult, singleEditCounter } from "./tool-results.js";
|
|
39
|
-
import { recordLocalToolCall, recordMcpSession } from "../core/telemetry.js";
|
|
41
|
+
import { recordLocalToolCall, recordMcpSession, toolErrorCode } from "../core/telemetry.js";
|
|
40
42
|
import { currentToolContext, registerMcpClientSource, runWithToolContext } from "../core/agent-host.js";
|
|
41
43
|
import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../installer/version-check.js";
|
|
42
44
|
import { TOOLKIT_VERSION as version } from "../core/version.js";
|
|
@@ -233,9 +235,9 @@ function installToolCallWrapper(server, surfaceMeta) {
|
|
|
233
235
|
// A tool another tool runs (summer_call, a v4 operation over its 3.x
|
|
234
236
|
// tool) is part of that call: only the outer call sends a usage line.
|
|
235
237
|
const nested = currentToolContext() !== undefined;
|
|
236
|
-
const recordUsage = (ok, durationMs) => {
|
|
238
|
+
const recordUsage = (ok, durationMs, error) => {
|
|
237
239
|
if (!nested)
|
|
238
|
-
recordLocalToolCall({ tool: name, op: opForToolCall(name, handlerArgs[0]), ...surfaceMeta, ok, durationMs, mcpClient: hostApp() });
|
|
240
|
+
recordLocalToolCall({ tool: name, op: knownOp(opForToolCall(name, handlerArgs[0])), ...surfaceMeta, ok, durationMs, mcpClient: hostApp(), error });
|
|
239
241
|
};
|
|
240
242
|
let result;
|
|
241
243
|
try {
|
|
@@ -253,7 +255,7 @@ function installToolCallWrapper(server, surfaceMeta) {
|
|
|
253
255
|
exception: error instanceof Error ? error.message : String(error),
|
|
254
256
|
durationMs,
|
|
255
257
|
});
|
|
256
|
-
recordUsage(false, durationMs);
|
|
258
|
+
recordUsage(false, durationMs, "exception");
|
|
257
259
|
throw error;
|
|
258
260
|
}
|
|
259
261
|
const durationMs = Date.now() - startedAt;
|
|
@@ -272,7 +274,7 @@ function installToolCallWrapper(server, surfaceMeta) {
|
|
|
272
274
|
}
|
|
273
275
|
try {
|
|
274
276
|
recordMcpSession();
|
|
275
|
-
recordUsage(!isError, durationMs);
|
|
277
|
+
recordUsage(!isError, durationMs, isError ? toolErrorCode(result, meta?.failureReason ?? meta?.errorClass) : null);
|
|
276
278
|
// Opt-in local trajectory capture: one JSONL line per tool call when
|
|
277
279
|
// SUMMER_TRAJECTORY_DIR is set. recordToolCall never throws and is a
|
|
278
280
|
// no-op when the env var is unset.
|
|
@@ -363,9 +365,10 @@ export function createMcpServer(selection = FULL_SELECTION, options = {}) {
|
|
|
363
365
|
name: "summer-engine",
|
|
364
366
|
version,
|
|
365
367
|
}, {
|
|
366
|
-
instructions: surface === "v4"
|
|
368
|
+
instructions: (surface === "v4"
|
|
367
369
|
? engine ? OPS_MCP_INSTRUCTIONS : OPS_MCP_ASSETS_INSTRUCTIONS
|
|
368
|
-
: engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS
|
|
370
|
+
: engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS) +
|
|
371
|
+
(options.notice ? ` ${options.notice} Tell the person once.` : ""),
|
|
369
372
|
});
|
|
370
373
|
const getWrappedToolCount = installToolCallWrapper(server, { surfaceVersion: surface, profile: selection.profile });
|
|
371
374
|
// Requests to Summer's servers name the agent app from the initialize handshake.
|
|
@@ -426,6 +429,32 @@ function hostedToolsLabel(toolsets) {
|
|
|
426
429
|
return "Tool Factory tools (summer_tool_*) and asset editing tools";
|
|
427
430
|
return undefined;
|
|
428
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* First v4 start after an upgrade: bring the installed Summer skills to this
|
|
434
|
+
* version before the legacy decision, which stays on while they are 3.x (and
|
|
435
|
+
* whenever the config asks for it). Not on the v3 surface, whose people chose
|
|
436
|
+
* the 3.x tools, and not under summer doctor's boot probe, whose session would
|
|
437
|
+
* swallow the one notice. A fault here never stops the server.
|
|
438
|
+
*/
|
|
439
|
+
export function refreshSkillsAfterUpgrade(surface) {
|
|
440
|
+
if (surface !== "v4" || process.env.SUMMER_MCP_DOCTOR === "1")
|
|
441
|
+
return null;
|
|
442
|
+
try {
|
|
443
|
+
const { refreshed, failures } = refreshInstalledSkills(version);
|
|
444
|
+
if (failures.length > 0)
|
|
445
|
+
appendMcpLogEvent("mcp:skills_refresh_failed", { version, failures });
|
|
446
|
+
const notice = refreshNotice(refreshed, failures);
|
|
447
|
+
if (notice) {
|
|
448
|
+
appendMcpLogEvent("mcp:skills_refreshed", { version, notice });
|
|
449
|
+
process.stderr.write(`[summer-mcp] ${notice}\n`);
|
|
450
|
+
}
|
|
451
|
+
return notice;
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
appendMcpLogEvent("mcp:skills_refresh_failed", { version, ...errorDetails(error) });
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
429
458
|
export async function startMcpServer(options = {}) {
|
|
430
459
|
configureMcpEngineSelection({
|
|
431
460
|
instanceId: options.instanceId ?? process.env.SUMMER_ENGINE_INSTANCE_ID,
|
|
@@ -445,6 +474,7 @@ export async function startMcpServer(options = {}) {
|
|
|
445
474
|
},
|
|
446
475
|
});
|
|
447
476
|
const surface = resolveSurface(options.surface);
|
|
477
|
+
const skillsNotice = refreshSkillsAfterUpgrade(surface);
|
|
448
478
|
// `legacy` is not a toolset of its own: it lists the 3.x names on the v4 surface.
|
|
449
479
|
const toolsetList = takeLegacyToolset(options.toolsets ?? process.env.SUMMER_TOOLSETS);
|
|
450
480
|
const legacy = surface === "v4" ? resolveLegacy(toolsetList.requested) : { on: false, reason: "off" };
|
|
@@ -459,7 +489,7 @@ export async function startMcpServer(options = {}) {
|
|
|
459
489
|
// v4 runs cloud operations through the hosted mount below, once it exists.
|
|
460
490
|
let hostedCall = null;
|
|
461
491
|
const cloud = { call: (name, args) => (hostedCall ? hostedCall(name, args) : NO_CLOUD.call(name, args)) };
|
|
462
|
-
const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on });
|
|
492
|
+
const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on, notice: skillsNotice });
|
|
463
493
|
// Fire-and-forget — never block tool registration on the npm registry.
|
|
464
494
|
void probeBootDrift().catch((error) => {
|
|
465
495
|
setCachedBootDriftNotice(null);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md and CLAUDE.md in a Summer project. AGENTS.md is the one file
|
|
3
|
+
* Codex, Cursor, Copilot, Amp and other agents read in every session;
|
|
4
|
+
* Claude Code reads CLAUDE.md, which here only imports AGENTS.md.
|
|
5
|
+
* `summer create` writes both; `summer setup --yes` inside a project adds
|
|
6
|
+
* them when missing.
|
|
7
|
+
*
|
|
8
|
+
* An existing file is never overwritten: Summer adds one marked block once,
|
|
9
|
+
* and later runs replace only that block. A link is followed only to a file
|
|
10
|
+
* inside the project, and a CLAUDE.md that is the same file as AGENTS.md
|
|
11
|
+
* gets the AGENTS.md block only. The five commands are the list in the
|
|
12
|
+
* summer-cli skill (between its project-commands markers), so the two
|
|
13
|
+
* cannot drift; the skill's test runs every command both of them teach.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SUMMER_BLOCK_START = "<!-- summer-engine:start. summer setup updates only this block; write your own notes outside it. -->";
|
|
16
|
+
export declare const SUMMER_BLOCK_END = "<!-- summer-engine:end -->";
|
|
17
|
+
/** The numbered command list from the summer-cli skill. */
|
|
18
|
+
export declare function projectCommands(skill?: string): string;
|
|
19
|
+
/** The Summer block of AGENTS.md, markers included. */
|
|
20
|
+
export declare function renderAgentsBlock(commands?: string): string;
|
|
21
|
+
export type InstructionAction = "created" | "added" | "updated" | "unchanged" | "skipped";
|
|
22
|
+
export interface InstructionFileResult {
|
|
23
|
+
file: "AGENTS.md" | "CLAUDE.md";
|
|
24
|
+
path: string;
|
|
25
|
+
action: InstructionAction;
|
|
26
|
+
/** Why a file was skipped or left as it is. */
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Write or update AGENTS.md and CLAUDE.md in the project root. A new
|
|
31
|
+
* AGENTS.md is the Summer block; a new CLAUDE.md is `@AGENTS.md` only. An
|
|
32
|
+
* existing CLAUDE.md that already imports AGENTS.md is left as it is. Each
|
|
33
|
+
* file is its own step: a file that cannot be read or written is skipped
|
|
34
|
+
* with the reason, and the other is still done.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeAgentInstructions(projectDir: string, options?: {
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
block?: string;
|
|
39
|
+
}): InstructionFileResult[];
|
|
40
|
+
/**
|
|
41
|
+
* The Summer project the working directory is in, for `summer setup`: the
|
|
42
|
+
* nearest folder with project.godot, looking up no further than the git
|
|
43
|
+
* top level, and never the home folder or the filesystem root (a stray
|
|
44
|
+
* project.godot there must not put an AGENTS.md or CLAUDE.md in every
|
|
45
|
+
* session). Null when there is none.
|
|
46
|
+
*/
|
|
47
|
+
export declare function findSetupProjectRoot(cwd?: string): string | null;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { basename, dirname, join, parse, relative, resolve, isAbsolute } from "path";
|
|
4
|
+
import { PACKAGE_ROOT } from "../core/package-root.js";
|
|
5
|
+
/**
|
|
6
|
+
* AGENTS.md and CLAUDE.md in a Summer project. AGENTS.md is the one file
|
|
7
|
+
* Codex, Cursor, Copilot, Amp and other agents read in every session;
|
|
8
|
+
* Claude Code reads CLAUDE.md, which here only imports AGENTS.md.
|
|
9
|
+
* `summer create` writes both; `summer setup --yes` inside a project adds
|
|
10
|
+
* them when missing.
|
|
11
|
+
*
|
|
12
|
+
* An existing file is never overwritten: Summer adds one marked block once,
|
|
13
|
+
* and later runs replace only that block. A link is followed only to a file
|
|
14
|
+
* inside the project, and a CLAUDE.md that is the same file as AGENTS.md
|
|
15
|
+
* gets the AGENTS.md block only. The five commands are the list in the
|
|
16
|
+
* summer-cli skill (between its project-commands markers), so the two
|
|
17
|
+
* cannot drift; the skill's test runs every command both of them teach.
|
|
18
|
+
*/
|
|
19
|
+
export const SUMMER_BLOCK_START = "<!-- summer-engine:start. summer setup updates only this block; write your own notes outside it. -->";
|
|
20
|
+
export const SUMMER_BLOCK_END = "<!-- summer-engine:end -->";
|
|
21
|
+
const CLAUDE_IMPORTS = new Set(["@AGENTS.md", "@./AGENTS.md"]);
|
|
22
|
+
const SKILL_PATH = ["library", "skills", "summer-cli", "SKILL.md"];
|
|
23
|
+
const COMMANDS = /<!-- project-commands[^>]*-->\n([\s\S]*?)\n<!-- \/project-commands -->/;
|
|
24
|
+
/** The numbered command list from the summer-cli skill. */
|
|
25
|
+
export function projectCommands(skill = readFileSync(join(PACKAGE_ROOT, ...SKILL_PATH), "utf-8")) {
|
|
26
|
+
const match = COMMANDS.exec(skill);
|
|
27
|
+
if (!match)
|
|
28
|
+
throw new Error("The summer-cli skill has no project-commands list.");
|
|
29
|
+
return match[1].trim();
|
|
30
|
+
}
|
|
31
|
+
/** The Summer block of AGENTS.md, markers included. */
|
|
32
|
+
export function renderAgentsBlock(commands = projectCommands()) {
|
|
33
|
+
return [
|
|
34
|
+
SUMMER_BLOCK_START,
|
|
35
|
+
"# Summer Engine game",
|
|
36
|
+
"",
|
|
37
|
+
"This is a game made with Summer Engine, the game engine built for AI. Game code is GDScript (`.gd`), scenes are `.tscn`, resources are `.tres`, and `project.godot` is the project file.",
|
|
38
|
+
"",
|
|
39
|
+
"## How to work on it",
|
|
40
|
+
"",
|
|
41
|
+
"- Change scenes through Summer while Summer Engine has the project open. The editor rewrites `.tscn` files when it saves, so hand edits get lost.",
|
|
42
|
+
"- With Summer's MCP server, call the `summer_*` tools and start with `summer_get_project_context`. Without it, run the same operations as `summer` commands in the shell (the `summer-cli` skill). If `summer` is missing or older than 4.0, use `npx -y summer-engine@latest` in its place.",
|
|
43
|
+
"- Summer's skills teach each part of a game. `npx -y summer-engine@latest setup <agent> --yes` installs them for your agent (add `--cli` for no MCP server). Start with `using-summer`, and find the right one with `summer docs search --query \"<the task>\"`.",
|
|
44
|
+
"- `.summer/` is the project's memory: `GameSoul.md` says what the game is, `memory/` holds the decisions to keep. Read them before you change the game's look, sound or story.",
|
|
45
|
+
"- Check your own work: script errors, a screenshot, a play run. Ask the person to play only for feel and fun.",
|
|
46
|
+
"",
|
|
47
|
+
"## The five commands",
|
|
48
|
+
"",
|
|
49
|
+
commands,
|
|
50
|
+
"",
|
|
51
|
+
"## Publishing",
|
|
52
|
+
"",
|
|
53
|
+
"The `publish-your-game` skill has the steps; `summer ops search \"publish my game\"` finds each operation. `summer store publish` sends the game to Summer's store review, and the game's owner approves it: give them the link it prints. Nothing goes live until they approve.",
|
|
54
|
+
SUMMER_BLOCK_END,
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
/** The text with `block` as its Summer block: replaced where it is, else appended. Keeps the file's line endings. */
|
|
58
|
+
function withBlock(text, block) {
|
|
59
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
60
|
+
const ours = block.replace(/\r?\n/g, eol);
|
|
61
|
+
const start = text.indexOf(SUMMER_BLOCK_START);
|
|
62
|
+
if (start === -1) {
|
|
63
|
+
// Only trailing line breaks change outside the block; an empty file is the block alone.
|
|
64
|
+
return { text: text.trim() ? `${text.replace(/(\r?\n)*$/, eol)}${eol}${ours}${eol}` : `${ours}${eol}`, action: "added" };
|
|
65
|
+
}
|
|
66
|
+
const end = text.indexOf(SUMMER_BLOCK_END, start);
|
|
67
|
+
if (end === -1)
|
|
68
|
+
return { text, action: "skipped", reason: "its Summer block has no end marker" };
|
|
69
|
+
// Two blocks (a merge, or the markers quoted in the person's text): which
|
|
70
|
+
// one is Summer's is not knowable, so the person picks; nothing is deleted.
|
|
71
|
+
if (text.indexOf(SUMMER_BLOCK_START, end) !== -1)
|
|
72
|
+
return { text, action: "skipped", reason: "it has two Summer blocks; delete one and run setup again" };
|
|
73
|
+
const next = `${text.slice(0, start)}${ours}${text.slice(end + SUMMER_BLOCK_END.length)}`;
|
|
74
|
+
return { text: next, action: next === text ? "unchanged" : "updated" };
|
|
75
|
+
}
|
|
76
|
+
/** Does the text import AGENTS.md on a line of its own, outside code fences? */
|
|
77
|
+
function importsAgents(text) {
|
|
78
|
+
let fence = false;
|
|
79
|
+
for (const line of text.split(/\r?\n/)) {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (/^(```|~~~)/.test(trimmed))
|
|
82
|
+
fence = !fence;
|
|
83
|
+
else if (!fence && CLAUDE_IMPORTS.has(trimmed))
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function inside(root, path) {
|
|
89
|
+
const rel = relative(root, path);
|
|
90
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Where a file may be written, and `real`, the file a write lands in:
|
|
94
|
+
* "missing" (create it: here, or the missing target of a link inside the
|
|
95
|
+
* project), "file" (a file, or a link to one inside the project), or a
|
|
96
|
+
* reason to leave it alone (a link that points nowhere or out of the
|
|
97
|
+
* project). `real` is known before anything is written, so a link from one
|
|
98
|
+
* name to the other counts as one file even when its target is missing.
|
|
99
|
+
*/
|
|
100
|
+
function locate(path, realRoot) {
|
|
101
|
+
let isLink = false;
|
|
102
|
+
try {
|
|
103
|
+
isLink = lstatSync(path).isSymbolicLink();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { kind: "missing", real: join(realRoot, basename(path)) };
|
|
107
|
+
}
|
|
108
|
+
let real;
|
|
109
|
+
try {
|
|
110
|
+
real = realpathSync(path);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
const target = resolve(realRoot, readlinkSync(path));
|
|
114
|
+
return inside(realRoot, target) ? { kind: "missing", real: target } : { kind: "skip", reason: "it is a link to a file that does not exist" };
|
|
115
|
+
}
|
|
116
|
+
if (isLink && !inside(realRoot, real))
|
|
117
|
+
return { kind: "skip", reason: "it is a link to a file outside the project" };
|
|
118
|
+
return { kind: "file", real };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Write or update AGENTS.md and CLAUDE.md in the project root. A new
|
|
122
|
+
* AGENTS.md is the Summer block; a new CLAUDE.md is `@AGENTS.md` only. An
|
|
123
|
+
* existing CLAUDE.md that already imports AGENTS.md is left as it is. Each
|
|
124
|
+
* file is its own step: a file that cannot be read or written is skipped
|
|
125
|
+
* with the reason, and the other is still done.
|
|
126
|
+
*/
|
|
127
|
+
export function writeAgentInstructions(projectDir, options = {}) {
|
|
128
|
+
const agents = join(projectDir, "AGENTS.md");
|
|
129
|
+
const claude = join(projectDir, "CLAUDE.md");
|
|
130
|
+
let block;
|
|
131
|
+
let realRoot;
|
|
132
|
+
try {
|
|
133
|
+
block = options.block ?? renderAgentsBlock();
|
|
134
|
+
realRoot = realpathSync(projectDir);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const reason = messageOf(error);
|
|
138
|
+
return [
|
|
139
|
+
{ file: "AGENTS.md", path: agents, action: "skipped", reason },
|
|
140
|
+
{ file: "CLAUDE.md", path: claude, action: "skipped", reason },
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
const write = (path, text) => {
|
|
144
|
+
if (!options.dryRun)
|
|
145
|
+
writeFileSync(path, text, "utf-8");
|
|
146
|
+
};
|
|
147
|
+
const step = (file, path, run) => {
|
|
148
|
+
try {
|
|
149
|
+
return { file, path, ...run() };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return { file, path, action: "skipped", reason: messageOf(error) };
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const agentsAt = locate(agents, realRoot);
|
|
156
|
+
const agentsResult = step("AGENTS.md", agents, () => {
|
|
157
|
+
if (agentsAt.kind === "skip")
|
|
158
|
+
return { action: "skipped", reason: agentsAt.reason };
|
|
159
|
+
if (agentsAt.kind === "missing") {
|
|
160
|
+
write(agents, `${block}\n`);
|
|
161
|
+
return { action: "created" };
|
|
162
|
+
}
|
|
163
|
+
const next = withBlock(readFileSync(agents, "utf-8"), block);
|
|
164
|
+
if (next.action === "added" || next.action === "updated")
|
|
165
|
+
write(agents, next.text);
|
|
166
|
+
return { action: next.action, ...(next.reason ? { reason: next.reason } : {}) };
|
|
167
|
+
});
|
|
168
|
+
const claudeAt = locate(claude, realRoot);
|
|
169
|
+
const claudeResult = step("CLAUDE.md", claude, () => {
|
|
170
|
+
if (claudeAt.kind === "skip")
|
|
171
|
+
return { action: "skipped", reason: claudeAt.reason };
|
|
172
|
+
// CLAUDE.md -> AGENTS.md (or the reverse): one file, which already has the AGENTS.md block.
|
|
173
|
+
if (agentsAt.kind !== "skip" && claudeAt.real === agentsAt.real) {
|
|
174
|
+
return { action: "unchanged", reason: "it is the same file as AGENTS.md" };
|
|
175
|
+
}
|
|
176
|
+
if (claudeAt.kind === "missing") {
|
|
177
|
+
write(claude, "@AGENTS.md\n");
|
|
178
|
+
return { action: "created" };
|
|
179
|
+
}
|
|
180
|
+
const text = readFileSync(claude, "utf-8");
|
|
181
|
+
if (importsAgents(text) && !text.includes(SUMMER_BLOCK_START))
|
|
182
|
+
return { action: "unchanged" };
|
|
183
|
+
const next = withBlock(text, `${SUMMER_BLOCK_START}\n@AGENTS.md\n${SUMMER_BLOCK_END}`);
|
|
184
|
+
if (next.action === "added" || next.action === "updated")
|
|
185
|
+
write(claude, next.text);
|
|
186
|
+
return { action: next.action, ...(next.reason ? { reason: next.reason } : {}) };
|
|
187
|
+
});
|
|
188
|
+
return [agentsResult, claudeResult];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The Summer project the working directory is in, for `summer setup`: the
|
|
192
|
+
* nearest folder with project.godot, looking up no further than the git
|
|
193
|
+
* top level, and never the home folder or the filesystem root (a stray
|
|
194
|
+
* project.godot there must not put an AGENTS.md or CLAUDE.md in every
|
|
195
|
+
* session). Null when there is none.
|
|
196
|
+
*/
|
|
197
|
+
export function findSetupProjectRoot(cwd = process.cwd()) {
|
|
198
|
+
let current;
|
|
199
|
+
try {
|
|
200
|
+
current = realpathSync(resolve(cwd));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
let home;
|
|
206
|
+
try {
|
|
207
|
+
home = realpathSync(homedir());
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
home = homedir();
|
|
211
|
+
}
|
|
212
|
+
while (current !== home && current !== parse(current).root) {
|
|
213
|
+
if (existsSync(join(current, "project.godot")))
|
|
214
|
+
return current;
|
|
215
|
+
if (existsSync(join(current, ".git")))
|
|
216
|
+
return null;
|
|
217
|
+
const parent = dirname(current);
|
|
218
|
+
if (parent === current)
|
|
219
|
+
return null;
|
|
220
|
+
current = parent;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function messageOf(error) {
|
|
225
|
+
return error instanceof Error ? error.message : String(error);
|
|
226
|
+
}
|
package/gemini-extension.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_generated": "GENERATED from integrations/gemini — do not edit; npm run generate:registry",
|
|
3
3
|
"name": "summer",
|
|
4
|
-
"version": "4.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "4.3.0",
|
|
5
|
+
"description": "The game engine built for AI. Any agent makes a multiplayer game and publishes it. The Summer Engine MCP server, context primer and game-dev skills.",
|
|
6
6
|
"contextFileName": "GEMINI.md",
|
|
7
7
|
"mcpServers": {
|
|
8
8
|
"summer-engine": {
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"./library/skills/summer-apps/",
|
|
109
109
|
"./library/skills/summer-apps-design/",
|
|
110
110
|
"./library/skills/summer-assets/",
|
|
111
|
+
"./library/skills/summer-cli/",
|
|
111
112
|
"./library/skills/summer-friends/",
|
|
112
113
|
"./library/skills/summer-leaderboards/",
|
|
113
114
|
"./library/skills/summer-match-results/",
|
package/integrations/README.md
CHANGED
|
@@ -58,7 +58,7 @@ are listed where they differ.
|
|
|
58
58
|
| claude-desktop | — | MCP: `~/Library/Application Support/Claude/claude_desktop_config.json`; Linux `~/.config/Claude/claude_desktop_config.json`; Windows `%APPDATA%/Claude/claude_desktop_config.json`; no skills folder (MCP only) |
|
|
59
59
|
| codex | `.codex-plugin/plugin.json` | MCP: `~/.codex/config.toml`; project `.codex/config.toml`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
|
|
60
60
|
| cursor | `.cursor-plugin/plugin.json` | MCP: `~/.cursor/mcp.json`; project `.cursor/mcp.json`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
|
|
61
|
-
| windsurf | — | MCP: `~/.
|
|
61
|
+
| windsurf | — | MCP: `~/.config/devin/mcp_config.json`; Windows `%APPDATA%/devin/mcp_config.json`; skills: `~/.agents/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
|
|
62
62
|
| antigravity | — | MCP: `~/.gemini/config/mcp_config.json`; project `.agents/mcp_config.json`; skills: `~/.gemini/config/skills`; project `.agents/skills` (`<skill>/SKILL.md`) |
|
|
63
63
|
| gemini (legacy) | `gemini-extension.json` | MCP: `~/.gemini/extensions/summer-engine/gemini-extension.json`; skills: `~/.gemini/extensions/summer-engine/skills` (`<skill>/SKILL.md`) |
|
|
64
64
|
| cline | — | MCP: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; Linux `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; Windows `%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; skills: `~/.cline/skills`; project `.cline/skills` (`<skill>/SKILL.md`) |
|
|
@@ -8,6 +8,12 @@ Generated by `scripts/generate-registry` from `library/**/resource.yaml`:
|
|
|
8
8
|
- `registry/generated/marketplace.claude.json` -> `.claude-plugin/marketplace.json`
|
|
9
9
|
(marketplace listing wrapper).
|
|
10
10
|
|
|
11
|
+
- `registry/generated/plugin.claude-directory.json` -> `plugins/claude/.claude-plugin/plugin.json`,
|
|
12
|
+
plus the rest of `plugins/claude/` (`scripts/generate-registry/mirrors.ts`): the same
|
|
13
|
+
plugin for Anthropic's plugin directory, from a folder of its own (under 512 files,
|
|
14
|
+
no symlinks), with the server and every `summer-engine@latest` pinned to this
|
|
15
|
+
version. Submit `plugins/claude` as the plugin path.
|
|
16
|
+
|
|
11
17
|
The MCP pointer `./.mcp.json` these manifests reference is generated as
|
|
12
18
|
well (`registry/generated/mcp.json -> .mcp.json`, owned by the claude target).
|
|
13
19
|
|