summer-engine 4.0.0 → 4.2.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 +1 -1
- package/CHANGELOG.md +37 -0
- package/README.md +20 -9
- 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/memory.js +6 -2
- package/dist/cli/commands/ops.d.ts +20 -3
- package/dist/cli/commands/ops.js +55 -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/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/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 +4 -0
- package/dist/core/ops/catalog.js +13 -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/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 +32 -3
- 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 -0
- 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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { closeSync, cpSync, existsSync, lstatSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getSkillRegistry, resolveSkillDir } from "../core/skills-registry.js";
|
|
4
|
+
import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
|
|
5
|
+
import { compareSemver, parseSemver, readKeptSkills, readSkillMarker, SKILL_VERSION_MARKER_FILENAME } from "./version-check.js";
|
|
6
|
+
/**
|
|
7
|
+
* After an upgrade, bring the Summer skills already installed on this
|
|
8
|
+
* computer up to this CLI's version. Skills written for 3.x name 3.x tools,
|
|
9
|
+
* and while their marker says 3.x the MCP server keeps the legacy toolset on
|
|
10
|
+
* (legacy-toolset.ts), which gives back most of 4.0's smaller tool list.
|
|
11
|
+
*
|
|
12
|
+
* Only what Summer installed and nobody changed is replaced, in a skills
|
|
13
|
+
* folder that carries Summer's `.summer-version` marker older than this CLI:
|
|
14
|
+
* a real folder (not a link) whose `resource.yaml` says `id: skill/<name>`
|
|
15
|
+
* (every Summer release ships it; other packs reuse names such as
|
|
16
|
+
* `brainstorming`), with no file changed after the marker was written, and
|
|
17
|
+
* not one an earlier refresh already left for that reason (the marker lists
|
|
18
|
+
* them, so the protection lasts until `summer setup --force`). A copy that
|
|
19
|
+
* already matches the library counts as current, not as changed. Each
|
|
20
|
+
* replacement is a copy beside it and two renames, so a kill or a copy error
|
|
21
|
+
* leaves the old skill in place. One process per folder (a lock file).
|
|
22
|
+
* Nothing is removed. Missing library skills are added only to a folder that
|
|
23
|
+
* holds nearly the whole library, so an assets-only install stays one.
|
|
24
|
+
* Rule-file installs (Cline, Roo Code, old Cursor rules) keep the refresh
|
|
25
|
+
* command `summer doctor` prints.
|
|
26
|
+
*/
|
|
27
|
+
const LOCK_FILE = ".summer-refresh.lock";
|
|
28
|
+
const STALE_LOCK_MS = 60_000;
|
|
29
|
+
/** Setup writes the marker right after its copy; a file changed later than this was changed by someone. */
|
|
30
|
+
const EDIT_GRACE_MS = 60_000;
|
|
31
|
+
/** A folder holding this share of the bundled library is a full install: new library skills join it. */
|
|
32
|
+
const FULL_INSTALL_SHARE = 0.9;
|
|
33
|
+
const TEMP_PREFIX = ".summer-refresh-";
|
|
34
|
+
const OLD_PREFIX = ".summer-old-";
|
|
35
|
+
/** User-scope skill folders with one folder per skill, each listed once. */
|
|
36
|
+
export function skillFolderLocations() {
|
|
37
|
+
const ctx = defaultPathContext();
|
|
38
|
+
const dirs = new Set([join(ctx.home, ".summer", "skills")]);
|
|
39
|
+
for (const spec of allAgentSpecs()) {
|
|
40
|
+
const home = resolveSkillPath(spec, "user", ctx);
|
|
41
|
+
if (home?.kind === "skill-dir")
|
|
42
|
+
dirs.add(home.path);
|
|
43
|
+
}
|
|
44
|
+
return [...dirs];
|
|
45
|
+
}
|
|
46
|
+
const isPrerelease = (version) => /^\d+\.\d+\.\d+-/.test(version.trim());
|
|
47
|
+
/** Refresh when the marker is older, or a pre-release of this release (4.2.0-next.1 -> 4.2.0, never back). */
|
|
48
|
+
function isBehind(marker, cliVersion) {
|
|
49
|
+
const installed = parseSemver(marker.version);
|
|
50
|
+
const current = parseSemver(cliVersion);
|
|
51
|
+
if (!installed || !current)
|
|
52
|
+
return false;
|
|
53
|
+
const order = compareSemver(installed, current);
|
|
54
|
+
return order < 0 || (order === 0 && isPrerelease(marker.version) && !isPrerelease(cliVersion));
|
|
55
|
+
}
|
|
56
|
+
const errorCode = (error) => error?.code ?? (error instanceof Error ? error.message : String(error));
|
|
57
|
+
/** Summer's own copy: a real folder (not a link) whose resource.yaml names this skill. */
|
|
58
|
+
function isSummersSkill(dest, name) {
|
|
59
|
+
try {
|
|
60
|
+
if (!lstatSync(dest).isDirectory())
|
|
61
|
+
return false;
|
|
62
|
+
const id = readFileSync(join(dest, "resource.yaml"), "utf-8").match(/^id:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1];
|
|
63
|
+
return id === `skill/${name}`;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** The folder, or any file in it, changed after `since` (ms); the folder's own time covers a deleted file. Links are not followed. */
|
|
70
|
+
function changedSince(dir, since) {
|
|
71
|
+
if (lstatSync(dir).mtimeMs > since)
|
|
72
|
+
return true;
|
|
73
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
74
|
+
const path = join(dir, entry.name);
|
|
75
|
+
if (lstatSync(path).mtimeMs > since)
|
|
76
|
+
return true;
|
|
77
|
+
if (entry.isDirectory() && changedSince(path, since))
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
/** Relative paths of every file under `dir`. */
|
|
83
|
+
function filesUnder(dir, prefix = "") {
|
|
84
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? filesUnder(join(dir, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]);
|
|
85
|
+
}
|
|
86
|
+
/** The installed folder holds exactly the library's files with the library's bytes (a refresh a kill interrupted already wrote it). */
|
|
87
|
+
function sameAsLibrary(dest, src) {
|
|
88
|
+
try {
|
|
89
|
+
const installed = filesUnder(dest).sort();
|
|
90
|
+
const library = filesUnder(src).sort();
|
|
91
|
+
return (installed.length === library.length &&
|
|
92
|
+
installed.every((file, index) => file === library[index] && readFileSync(join(dest, file)).equals(readFileSync(join(src, file)))));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Copy beside, rename the old one aside, rename the new one in; the old one comes back on any error. */
|
|
99
|
+
function replaceSkill(dir, name, src) {
|
|
100
|
+
const dest = join(dir, name);
|
|
101
|
+
const temp = join(dir, `${TEMP_PREFIX}${name}-${process.pid}`);
|
|
102
|
+
const old = join(dir, `${OLD_PREFIX}${name}-${process.pid}`);
|
|
103
|
+
rmSync(temp, { recursive: true, force: true });
|
|
104
|
+
try {
|
|
105
|
+
cpSync(src, temp, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
rmSync(temp, { recursive: true, force: true });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
const hadOld = existsSync(dest);
|
|
112
|
+
if (hadOld)
|
|
113
|
+
renameSync(dest, old);
|
|
114
|
+
try {
|
|
115
|
+
renameSync(temp, dest);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (hadOld)
|
|
119
|
+
renameSync(old, dest);
|
|
120
|
+
rmSync(temp, { recursive: true, force: true });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
if (hadOld)
|
|
124
|
+
rmSync(old, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
/** What a killed refresh left: half copies go, a skill renamed aside comes back. */
|
|
127
|
+
function recoverInterrupted(dir) {
|
|
128
|
+
for (const entry of readdirSync(dir)) {
|
|
129
|
+
const path = join(dir, entry);
|
|
130
|
+
if (entry.startsWith(TEMP_PREFIX))
|
|
131
|
+
rmSync(path, { recursive: true, force: true });
|
|
132
|
+
if (!entry.startsWith(OLD_PREFIX))
|
|
133
|
+
continue;
|
|
134
|
+
const name = entry.slice(OLD_PREFIX.length).replace(/-\d+$/, "");
|
|
135
|
+
if (existsSync(join(dir, name)))
|
|
136
|
+
rmSync(path, { recursive: true, force: true });
|
|
137
|
+
else
|
|
138
|
+
renameSync(path, join(dir, name));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** The folder's lock, or null while another process holds a fresh one. */
|
|
142
|
+
function takeLock(dir) {
|
|
143
|
+
const path = join(dir, LOCK_FILE);
|
|
144
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
145
|
+
try {
|
|
146
|
+
closeSync(openSync(path, "wx"));
|
|
147
|
+
return () => rmSync(path, { force: true });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (errorCode(error) !== "EEXIST")
|
|
151
|
+
throw error;
|
|
152
|
+
try {
|
|
153
|
+
if (Date.now() - statSync(path).mtimeMs < STALE_LOCK_MS)
|
|
154
|
+
return null;
|
|
155
|
+
unlinkSync(path);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function refreshFolder(dir, marker, cliVersion, registry, failures) {
|
|
165
|
+
const release = takeLock(dir);
|
|
166
|
+
if (!release)
|
|
167
|
+
return null;
|
|
168
|
+
try {
|
|
169
|
+
recoverInterrupted(dir);
|
|
170
|
+
// The marker must be writable before anything is copied, or every start would copy again.
|
|
171
|
+
const markerTemp = join(dir, `${SKILL_VERSION_MARKER_FILENAME}.tmp`);
|
|
172
|
+
writeFileSync(markerTemp, "", "utf-8");
|
|
173
|
+
const installedAt = Date.parse(marker.installedAt);
|
|
174
|
+
const editedAfter = (Number.isFinite(installedAt) ? installedAt : 0) + EDIT_GRACE_MS;
|
|
175
|
+
const folder = { dir, from: marker.version, to: cliVersion, updated: [], added: [], kept: [] };
|
|
176
|
+
const keptBefore = new Set(readKeptSkills(dir));
|
|
177
|
+
const owned = registry.filter((entry) => isSummersSkill(join(dir, entry.name), entry.name));
|
|
178
|
+
for (const entry of owned) {
|
|
179
|
+
const dest = join(dir, entry.name);
|
|
180
|
+
if (sameAsLibrary(dest, resolveSkillDir(entry))) {
|
|
181
|
+
folder.updated.push(entry.name);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (keptBefore.has(entry.name) || changedSince(dest, editedAfter)) {
|
|
185
|
+
folder.kept.push(entry.name);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
replaceSkill(dir, entry.name, resolveSkillDir(entry));
|
|
190
|
+
folder.updated.push(entry.name);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
failures.push({ dir, skill: entry.name, code: errorCode(error) });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const bundled = registry.filter((entry) => entry.bundled !== false && entry.status !== "deprecated");
|
|
197
|
+
if (bundled.length > 0 && owned.length >= FULL_INSTALL_SHARE * bundled.length) {
|
|
198
|
+
for (const entry of bundled) {
|
|
199
|
+
if (existsSync(join(dir, entry.name)))
|
|
200
|
+
continue;
|
|
201
|
+
try {
|
|
202
|
+
replaceSkill(dir, entry.name, resolveSkillDir(entry));
|
|
203
|
+
folder.added.push(entry.name);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
failures.push({ dir, skill: entry.name, code: errorCode(error) });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Written last, through a rename: a kill before this leaves the old marker, and the next start finishes.
|
|
211
|
+
const next = { version: cliVersion, installedAt: new Date().toISOString(), ...(folder.kept.length > 0 ? { kept: folder.kept } : {}) };
|
|
212
|
+
writeFileSync(markerTemp, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
213
|
+
renameSync(markerTemp, join(dir, SKILL_VERSION_MARKER_FILENAME));
|
|
214
|
+
return folder;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
release();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function refreshInstalledSkills(cliVersion, options = {}) {
|
|
221
|
+
const result = { refreshed: [], failures: [] };
|
|
222
|
+
if (!parseSemver(cliVersion))
|
|
223
|
+
return result;
|
|
224
|
+
for (const dir of options.dirs ?? skillFolderLocations()) {
|
|
225
|
+
const marker = readSkillMarker(dir);
|
|
226
|
+
if (!marker || !isBehind(marker, cliVersion))
|
|
227
|
+
continue;
|
|
228
|
+
try {
|
|
229
|
+
const folder = refreshFolder(dir, marker, cliVersion, options.registry ?? getSkillRegistry(), result.failures);
|
|
230
|
+
if (folder)
|
|
231
|
+
result.refreshed.push(folder);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
// A folder we cannot lock or write keeps its skills and its marker; doctor still names the refresh.
|
|
235
|
+
result.failures.push({ dir, code: errorCode(error) });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
const listed = (names) => names.length > 5 ? `${names.slice(0, 5).join(", ")} and ${names.length - 5} more` : names.join(", ");
|
|
241
|
+
/** The one message a person and their agent see after a refresh, or null when nothing changed. */
|
|
242
|
+
export function refreshNotice(refreshed, failures = []) {
|
|
243
|
+
const skills = new Set(refreshed.flatMap((folder) => [...folder.updated, ...folder.added])).size;
|
|
244
|
+
const kept = [...new Set(refreshed.flatMap((folder) => folder.kept))];
|
|
245
|
+
const failed = [...new Set(failures.flatMap((failure) => (failure.skill ? [failure.skill] : [])))];
|
|
246
|
+
if (skills === 0 && kept.length === 0)
|
|
247
|
+
return null;
|
|
248
|
+
const changedFolders = refreshed.filter((folder) => folder.updated.length + folder.added.length > 0).length;
|
|
249
|
+
const from = [...new Set(refreshed.map((folder) => folder.from))].join(", ");
|
|
250
|
+
const to = refreshed[0].to;
|
|
251
|
+
return [
|
|
252
|
+
skills > 0 ? `Summer updated its ${skills} installed skills (${changedFolders === 1 ? "1 folder" : `${changedFolders} folders`}) from ${from} to ${to}.` : "",
|
|
253
|
+
kept.length > 0
|
|
254
|
+
? `It left ${kept.length} Summer skills you changed as they are (${listed(kept)}); summer setup <agent> --force replaces them.`
|
|
255
|
+
: "",
|
|
256
|
+
failed.length > 0 ? `${failed.length} could not be updated (${listed(failed)}); summer setup <agent> --force retries them.` : "",
|
|
257
|
+
"3.x tool names still run through summer_call; summer mcp --toolsets legacy lists them again.",
|
|
258
|
+
]
|
|
259
|
+
.filter(Boolean)
|
|
260
|
+
.join(" ");
|
|
261
|
+
}
|
|
@@ -79,9 +79,18 @@ export declare const SKILL_VERSION_MARKER_FILENAME = ".summer-version";
|
|
|
79
79
|
export interface SkillVersionMarker {
|
|
80
80
|
version: string;
|
|
81
81
|
installedAt: string;
|
|
82
|
+
/** Summer skills the upgrade refresh left because someone changed them (installer/skills-refresh.ts). */
|
|
83
|
+
kept?: string[];
|
|
82
84
|
}
|
|
83
85
|
export declare function readSkillMarker(dir: string): SkillVersionMarker | null;
|
|
84
|
-
|
|
86
|
+
/** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
|
|
87
|
+
export declare function readKeptSkills(dir: string): string[];
|
|
88
|
+
/**
|
|
89
|
+
* Write the folder's marker. The skills an upgrade refresh left stay listed
|
|
90
|
+
* (so later refreshes keep leaving them), except the ones this install just
|
|
91
|
+
* wrote: `installed` names them; leave it out to keep every one.
|
|
92
|
+
*/
|
|
93
|
+
export declare function writeSkillMarker(dir: string, version: string, now?: Date, installed?: readonly string[]): string;
|
|
85
94
|
/**
|
|
86
95
|
* Candidate locations for skill-version markers. Listed roughly in order of
|
|
87
96
|
* how likely a Summer user is to have them populated. We probe every candidate,
|
|
@@ -108,6 +117,10 @@ export interface RecordedInstall {
|
|
|
108
117
|
/** Where the answer came from: the agent's config file, or — when it has
|
|
109
118
|
* no readable Summer entry — the nature of the CLI running this check. */
|
|
110
119
|
source: "agent-config" | "running-cli";
|
|
120
|
+
/** The agent's config lists no summer-engine server in any shape (`setup
|
|
121
|
+
* --cli`, or skills installed alone) and `setup --cli` can set the agent
|
|
122
|
+
* up: the refresh keeps it without an MCP server. */
|
|
123
|
+
cliOnly?: boolean;
|
|
111
124
|
}
|
|
112
125
|
/** An npm dist-tag (`latest`, `next`) or an exact version (`3.9.1`). */
|
|
113
126
|
export type RefreshChannel = string;
|
|
@@ -153,7 +166,8 @@ export declare function buildSkillsVersionCheck(input: SkillsVersionCheckInput):
|
|
|
153
166
|
* The command that refreshes an agent's skills without changing how its MCP
|
|
154
167
|
* entry was recorded: the local-dev form re-links the same checkout
|
|
155
168
|
* (`setup --local-dev --force` re-copies the checkout's skills); the npx form
|
|
156
|
-
* is for installs that already run the published package.
|
|
169
|
+
* is for installs that already run the published package. An agent with no
|
|
170
|
+
* summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
|
|
157
171
|
*/
|
|
158
172
|
export declare function skillsRefreshCommand(agent: string, install: RecordedInstall | null, channel?: RefreshChannel): string;
|
|
159
173
|
/** The shape `summer setup --local-dev` writes: `node <…>/dist/bin/summer.js mcp`
|
|
@@ -173,6 +187,12 @@ export declare function isLocalDevServerConfig(server: StdioMcpServerConfig): bo
|
|
|
173
187
|
* Returns null when the file is missing, unparsable, or has no Summer entry.
|
|
174
188
|
*/
|
|
175
189
|
export declare function readRecordedMcpServer(text: string, format: McpFormat): StdioMcpServerConfig | null;
|
|
190
|
+
/**
|
|
191
|
+
* Does the agent's config list a summer-engine server in any shape: the stdio
|
|
192
|
+
* entry setup writes, a URL entry under that name, or (Claude Code) a
|
|
193
|
+
* per-project entry under projects.<dir>.mcpServers?
|
|
194
|
+
*/
|
|
195
|
+
export declare function listsSummerEngine(text: string, format: McpFormat): boolean;
|
|
176
196
|
/** Is the CLI running this check a source checkout rather than an installed
|
|
177
197
|
* package? npm/npx/global installs all live under a node_modules directory. */
|
|
178
198
|
export declare function runningCliIsCheckout(packageRoot?: string): boolean;
|
|
@@ -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
|
@@ -34,6 +34,7 @@ import { opForToolCall } from "../core/ops/contract.js";
|
|
|
34
34
|
import { v4HostedUrl } from "../core/ops/cloud.js";
|
|
35
35
|
import { serveProviderSafeToolSchemas } from "./provider-safe-schema.js";
|
|
36
36
|
import { legacyHostedDescription, resolveLegacy, takeLegacyToolset } from "./legacy-toolset.js";
|
|
37
|
+
import { refreshInstalledSkills, refreshNotice } from "../installer/skills-refresh.js";
|
|
37
38
|
import { OUTPUT_CAPS, PROFILE_TOOL_LIMIT, PROFILES, TOOLSETS, annotationsFor, registryAuthorities, resolveToolSelection, selectionIncludes, withMaxCharactersArg, } from "./tool-surface.js";
|
|
38
39
|
import { shapeToolResult, singleEditCounter } from "./tool-results.js";
|
|
39
40
|
import { recordLocalToolCall, recordMcpSession } from "../core/telemetry.js";
|
|
@@ -363,9 +364,10 @@ export function createMcpServer(selection = FULL_SELECTION, options = {}) {
|
|
|
363
364
|
name: "summer-engine",
|
|
364
365
|
version,
|
|
365
366
|
}, {
|
|
366
|
-
instructions: surface === "v4"
|
|
367
|
+
instructions: (surface === "v4"
|
|
367
368
|
? engine ? OPS_MCP_INSTRUCTIONS : OPS_MCP_ASSETS_INSTRUCTIONS
|
|
368
|
-
: engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS
|
|
369
|
+
: engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS) +
|
|
370
|
+
(options.notice ? ` ${options.notice} Tell the person once.` : ""),
|
|
369
371
|
});
|
|
370
372
|
const getWrappedToolCount = installToolCallWrapper(server, { surfaceVersion: surface, profile: selection.profile });
|
|
371
373
|
// Requests to Summer's servers name the agent app from the initialize handshake.
|
|
@@ -426,6 +428,32 @@ function hostedToolsLabel(toolsets) {
|
|
|
426
428
|
return "Tool Factory tools (summer_tool_*) and asset editing tools";
|
|
427
429
|
return undefined;
|
|
428
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* First v4 start after an upgrade: bring the installed Summer skills to this
|
|
433
|
+
* version before the legacy decision, which stays on while they are 3.x (and
|
|
434
|
+
* whenever the config asks for it). Not on the v3 surface, whose people chose
|
|
435
|
+
* the 3.x tools, and not under summer doctor's boot probe, whose session would
|
|
436
|
+
* swallow the one notice. A fault here never stops the server.
|
|
437
|
+
*/
|
|
438
|
+
export function refreshSkillsAfterUpgrade(surface) {
|
|
439
|
+
if (surface !== "v4" || process.env.SUMMER_MCP_DOCTOR === "1")
|
|
440
|
+
return null;
|
|
441
|
+
try {
|
|
442
|
+
const { refreshed, failures } = refreshInstalledSkills(version);
|
|
443
|
+
if (failures.length > 0)
|
|
444
|
+
appendMcpLogEvent("mcp:skills_refresh_failed", { version, failures });
|
|
445
|
+
const notice = refreshNotice(refreshed, failures);
|
|
446
|
+
if (notice) {
|
|
447
|
+
appendMcpLogEvent("mcp:skills_refreshed", { version, notice });
|
|
448
|
+
process.stderr.write(`[summer-mcp] ${notice}\n`);
|
|
449
|
+
}
|
|
450
|
+
return notice;
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
appendMcpLogEvent("mcp:skills_refresh_failed", { version, ...errorDetails(error) });
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
429
457
|
export async function startMcpServer(options = {}) {
|
|
430
458
|
configureMcpEngineSelection({
|
|
431
459
|
instanceId: options.instanceId ?? process.env.SUMMER_ENGINE_INSTANCE_ID,
|
|
@@ -445,6 +473,7 @@ export async function startMcpServer(options = {}) {
|
|
|
445
473
|
},
|
|
446
474
|
});
|
|
447
475
|
const surface = resolveSurface(options.surface);
|
|
476
|
+
const skillsNotice = refreshSkillsAfterUpgrade(surface);
|
|
448
477
|
// `legacy` is not a toolset of its own: it lists the 3.x names on the v4 surface.
|
|
449
478
|
const toolsetList = takeLegacyToolset(options.toolsets ?? process.env.SUMMER_TOOLSETS);
|
|
450
479
|
const legacy = surface === "v4" ? resolveLegacy(toolsetList.requested) : { on: false, reason: "off" };
|
|
@@ -459,7 +488,7 @@ export async function startMcpServer(options = {}) {
|
|
|
459
488
|
// v4 runs cloud operations through the hosted mount below, once it exists.
|
|
460
489
|
let hostedCall = null;
|
|
461
490
|
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 });
|
|
491
|
+
const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on, notice: skillsNotice });
|
|
463
492
|
// Fire-and-forget — never block tool registration on the npm registry.
|
|
464
493
|
void probeBootDrift().catch((error) => {
|
|
465
494
|
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;
|