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
package/dist/core/ops/search.js
CHANGED
|
@@ -1,53 +1,185 @@
|
|
|
1
|
+
import { stem } from "../registry-search.js";
|
|
1
2
|
/**
|
|
2
|
-
* Plain-words ranking over operations
|
|
3
|
-
*
|
|
3
|
+
* Plain-words ranking over operations. cloud.ts ranks the local rows, the
|
|
4
|
+
* shipped cloud catalog and the server's live rows together here, so a query
|
|
5
|
+
* ranks the same signed in or out.
|
|
6
|
+
*
|
|
7
|
+
* - A query word scores by the best place it meets a row: the action (the
|
|
8
|
+
* name after the dot) 7, the area or the title 4, a short tag 3, a 3.x name
|
|
9
|
+
* 2, the summary or a long use-when line 1. An exact word counts whole, a
|
|
10
|
+
* stemmed one 0.9, a prefix half, a plain-English synonym (SYNONYMS) 0.7.
|
|
11
|
+
* - Rare words weigh more (idf over the rows searched) and everyday verbs
|
|
12
|
+
* half: in "publish my game" publish decides, in "add multiplayer"
|
|
13
|
+
* multiplayer does.
|
|
14
|
+
* - A row that meets every word in its name, title or short tags keeps its
|
|
15
|
+
* score; one that meets none of them there keeps half. One whose whole
|
|
16
|
+
* action the query names gains a quarter: "publish" is store.publish
|
|
17
|
+
* before store.cancel_publish.
|
|
18
|
+
* - A title or tag the query says nearly word for word, articles aside
|
|
19
|
+
* ("how is my game doing" for "how did my game do"), adds 2 per word.
|
|
4
20
|
*/
|
|
5
|
-
const STOP = new Set([
|
|
21
|
+
const STOP = new Set([
|
|
22
|
+
"a", "an", "the", "my", "of", "for", "to", "in", "on", "and", "or", "with", "is", "it", "me", "i", "please", "how", "do", "what",
|
|
23
|
+
"did", "does", "doing", "done", "am", "are", "was", "were", "be", "have", "has", "had", "can", "could", "would", "should", "will",
|
|
24
|
+
"want", "need", "this", "that", "these", "those", "some", "any", "many", "much", "why", "where", "when", "which", "there", "you",
|
|
25
|
+
"your", "we", "our", "us", "at", "by", "from", "as", "so", "just", "s",
|
|
26
|
+
]);
|
|
27
|
+
/** Verbs that say what to do but rarely which operation: the object does. */
|
|
28
|
+
const EVERYDAY_VERBS = new Set(["add", "make", "create", "set", "get", "change", "update", "edit", "run", "start", "put", "take", "show", "read", "list", "use", "give", "check", "find", "see", "turn", "open"]);
|
|
29
|
+
/** Left out of phrase matching: "put my game on sale" says "Put a game on sale". */
|
|
30
|
+
const ARTICLES = new Set(["a", "an", "the", "my", "your", "our", "their", "its", "this", "that"]);
|
|
31
|
+
/** Irregular forms, so "how did my game do" meets "how is my game doing". */
|
|
32
|
+
const IRREGULAR = { did: "do", does: "do", doing: "do", done: "do", is: "be", are: "be", was: "be", were: "be", am: "be", has: "have", had: "have" };
|
|
33
|
+
/** Plain English to the catalog's words (stemmed keys). */
|
|
34
|
+
const SYNONYMS = {
|
|
35
|
+
make: ["generate", "create"],
|
|
36
|
+
create: ["generate", "make"],
|
|
37
|
+
picture: ["image"],
|
|
38
|
+
drawing: ["image"],
|
|
39
|
+
photo: ["image"],
|
|
40
|
+
model: ["3d"],
|
|
41
|
+
mesh: ["3d"],
|
|
42
|
+
sound: ["audio"],
|
|
43
|
+
music: ["audio"],
|
|
44
|
+
song: ["audio"],
|
|
45
|
+
clip: ["video"],
|
|
46
|
+
movie: ["video"],
|
|
47
|
+
film: ["video"],
|
|
48
|
+
animate: ["motion"],
|
|
49
|
+
animation: ["motion"],
|
|
50
|
+
run: ["play", "start"],
|
|
51
|
+
cost: ["price"],
|
|
52
|
+
teammate: ["member"],
|
|
53
|
+
character: ["model"],
|
|
54
|
+
game: ["store"],
|
|
55
|
+
error: ["diagnostic"],
|
|
56
|
+
bug: ["error", "diagnostic"],
|
|
57
|
+
};
|
|
58
|
+
const norm = (word) => IRREGULAR[word] ?? stem(word);
|
|
59
|
+
const tokens = (text) => text
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/\ba\/b\b/g, "ab")
|
|
62
|
+
.split(/[^a-z0-9]+/)
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.map((raw) => ({ raw, normed: norm(raw) }));
|
|
6
65
|
export function words(text) {
|
|
7
|
-
return text
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
66
|
+
return tokens(text)
|
|
67
|
+
.filter(({ raw }) => raw.length > 1 && !STOP.has(raw))
|
|
68
|
+
.map(({ raw }) => raw);
|
|
69
|
+
}
|
|
70
|
+
const indexCache = new WeakMap();
|
|
71
|
+
function indexed(entry) {
|
|
72
|
+
const cached = indexCache.get(entry);
|
|
73
|
+
if (cached)
|
|
74
|
+
return cached;
|
|
75
|
+
const [area, action] = entry.name.includes(".") ? entry.name.split(".") : ["", entry.name];
|
|
76
|
+
const kept = (text) => tokens(text).filter(({ raw }) => raw.length > 1 && !STOP.has(raw));
|
|
77
|
+
const tags = entry.tags ?? [];
|
|
78
|
+
// A tag of a few words is a keyword; a long one (a use-when line) reads like the summary.
|
|
79
|
+
const short = (tag) => tokens(tag).length <= 3;
|
|
20
80
|
const fields = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[(entry.
|
|
25
|
-
|
|
81
|
+
{ weight: 7, words: kept(action.replace(/_/g, " ")) },
|
|
82
|
+
{ weight: 4, words: kept(area) },
|
|
83
|
+
{ weight: 4, words: kept(entry.title) },
|
|
84
|
+
{ weight: 3, words: [...tags.filter(short), ...(entry.topics ?? [])].flatMap(kept) },
|
|
85
|
+
{ weight: 2, words: (entry.legacy ?? []).flatMap((name) => kept(name.replace(/^summer_/, "").replace(/[#_]/g, " "))) },
|
|
86
|
+
{ weight: 1, words: [entry.summary, ...tags.filter((tag) => !short(tag))].flatMap(kept) },
|
|
26
87
|
];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
88
|
+
const phrases = [entry.title, ...tags].map((text) => tokens(text).filter(({ raw }) => !ARTICLES.has(raw))).filter((phrase) => phrase.length >= 2);
|
|
89
|
+
const result = { fields, phrases, action: kept(action.replace(/_/g, " ")), area: kept(area) };
|
|
90
|
+
indexCache.set(entry, result);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
/** How well one query word meets one row word: exact 1, same stem 0.9, a prefix 0.5 ("anim" in animation; not "play" in players). */
|
|
94
|
+
function meets(term, word) {
|
|
95
|
+
if (word.raw === term.raw)
|
|
96
|
+
return 1;
|
|
97
|
+
if (word.normed === term.normed)
|
|
98
|
+
return 0.9;
|
|
99
|
+
const [a, b] = [term.normed, word.normed];
|
|
100
|
+
return (a.length >= 4 && b.startsWith(a)) || (b.length >= 5 && a.startsWith(b)) ? 0.5 : 0;
|
|
101
|
+
}
|
|
102
|
+
const alternatesOf = (term) => (SYNONYMS[term.normed] ?? []).map((synonym) => ({ raw: synonym, normed: norm(synonym) }));
|
|
103
|
+
/** A query word's best weight in a row, and whether it met a strong field (weight 3 or more). */
|
|
104
|
+
function best(term, entry, synonyms = true) {
|
|
105
|
+
const alternates = synonyms ? alternatesOf(term) : [];
|
|
106
|
+
let score = 0;
|
|
107
|
+
let strong = false;
|
|
108
|
+
for (const field of indexed(entry).fields) {
|
|
109
|
+
for (const word of field.words) {
|
|
110
|
+
const quality = Math.max(meets(term, word), ...alternates.map((alternate) => 0.7 * meets(alternate, word)));
|
|
111
|
+
if (quality === 0)
|
|
112
|
+
continue;
|
|
113
|
+
score = Math.max(score, field.weight * quality);
|
|
114
|
+
if (field.weight >= 3)
|
|
115
|
+
strong = true;
|
|
38
116
|
}
|
|
39
|
-
total += best;
|
|
40
117
|
}
|
|
41
|
-
return
|
|
118
|
+
return { score, strong };
|
|
119
|
+
}
|
|
120
|
+
/** The best title or tag the query says nearly word for word: 2 per word met. */
|
|
121
|
+
function phraseBonus(entry, all) {
|
|
122
|
+
const query = all.filter(({ raw }) => !ARTICLES.has(raw));
|
|
123
|
+
let bonus = 0;
|
|
124
|
+
for (const phrase of indexed(entry).phrases) {
|
|
125
|
+
const met = phrase.map((word) => Math.max(0, ...query.map((term) => (term.raw === word.raw ? 1 : term.normed === word.normed ? 0.9 : alternatesOf(term).some((alternate) => alternate.normed === word.normed) ? 0.7 : 0))));
|
|
126
|
+
const count = met.filter((quality) => quality > 0).length;
|
|
127
|
+
const meaningful = phrase.some((word, index) => met[index] > 0 && !STOP.has(word.raw));
|
|
128
|
+
if (meaningful && count >= 2 && count / phrase.length >= 0.75)
|
|
129
|
+
bonus = Math.max(bonus, 2 * met.reduce((sum, quality) => sum + quality, 0));
|
|
130
|
+
}
|
|
131
|
+
return bonus;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The query names the row's whole action, and its area too when the action
|
|
135
|
+
* is only an everyday verb: "list my games" names games.list, "run my game"
|
|
136
|
+
* does not name engine.is_running.
|
|
137
|
+
*/
|
|
138
|
+
function namesAction(entry, terms) {
|
|
139
|
+
const { action, area } = indexed(entry);
|
|
140
|
+
const met = (word) => terms.some((term) => meets(term, word) >= 0.9);
|
|
141
|
+
return action.length > 0 && action.every(met) && (action.some((word) => !EVERYDAY_VERBS.has(word.normed)) || area.some(met));
|
|
142
|
+
}
|
|
143
|
+
function scoreAll(entries, query) {
|
|
144
|
+
const exact = query.trim().toLowerCase();
|
|
145
|
+
const all = tokens(query);
|
|
146
|
+
const terms = all.filter(({ raw }) => raw.length > 1 && !STOP.has(raw));
|
|
147
|
+
if (!terms.length)
|
|
148
|
+
return entries.map(() => 0);
|
|
149
|
+
// Rarer words weigh more: idf over the rows searched; everyday verbs count half.
|
|
150
|
+
const weightOf = terms.map((term) => {
|
|
151
|
+
const df = entries.filter((entry) => best(term, entry, false).score > 0).length;
|
|
152
|
+
return Math.log(1 + entries.length / (1 + df)) * (EVERYDAY_VERBS.has(term.normed) ? 0.5 : 1);
|
|
153
|
+
});
|
|
154
|
+
return entries.map((entry) => {
|
|
155
|
+
if (exact === entry.name || entry.legacy?.some((name) => name.split("#")[0] === exact))
|
|
156
|
+
return 1000;
|
|
157
|
+
let total = 0;
|
|
158
|
+
let strong = 0;
|
|
159
|
+
let missed = false;
|
|
160
|
+
terms.forEach((term, index) => {
|
|
161
|
+
const hit = best(term, entry);
|
|
162
|
+
total += weightOf[index] * hit.score;
|
|
163
|
+
if (hit.strong)
|
|
164
|
+
strong += 1;
|
|
165
|
+
if (hit.score === 0)
|
|
166
|
+
missed = true;
|
|
167
|
+
});
|
|
168
|
+
if (total === 0)
|
|
169
|
+
return 0;
|
|
170
|
+
return total * (0.5 + (0.5 * strong) / terms.length) * (!missed && namesAction(entry, terms) ? 1.25 : 1) + phraseBonus(entry, all);
|
|
171
|
+
});
|
|
42
172
|
}
|
|
43
173
|
const STATUS_RANK = { stable: 0, preview: 1, deprecated: 2 };
|
|
44
174
|
export function searchEntries(entries, query, filters) {
|
|
45
175
|
const hasTerms = words(query).length > 0;
|
|
46
|
-
|
|
176
|
+
const kept = entries
|
|
47
177
|
.filter((entry) => entry.status !== "deprecated")
|
|
48
178
|
.filter((entry) => !filters.area || entry.name === filters.area || entry.name.startsWith(`${filters.area}.`))
|
|
49
|
-
.filter((entry) => !filters.effect || entry.effect === filters.effect)
|
|
50
|
-
|
|
179
|
+
.filter((entry) => !filters.effect || entry.effect === filters.effect);
|
|
180
|
+
const scores = scoreAll(kept, query);
|
|
181
|
+
return kept
|
|
182
|
+
.map((entry, index) => ({ entry, score: scores[index] }))
|
|
51
183
|
.filter(({ score }) => score > 0 || !hasTerms)
|
|
52
184
|
.sort((a, b) => b.score - a.score || STATUS_RANK[a.entry.status] - STATUS_RANK[b.entry.status] || Number(b.entry.available) - Number(a.entry.available) || a.entry.name.localeCompare(b.entry.name))
|
|
53
185
|
.slice(0, filters.limit)
|
|
@@ -52,6 +52,11 @@ export async function configureAgentMcp(options) {
|
|
|
52
52
|
const profile = options.profile ?? spec.mcpProfile ?? "full";
|
|
53
53
|
const server = withProfile(createSummerMcpServerConfig(Boolean(options.localDev), process.platform, channel), profile);
|
|
54
54
|
const target = resolveConfigTarget(spec, options.scope, cwd, env);
|
|
55
|
+
// An entry an earlier setup wrote where the agent no longer reads (Devin Desktop moved from ~/.codeium/windsurf).
|
|
56
|
+
const moved = spec.mcp.movedFrom ? spec.mcp.movedFrom(defaultPathContext(env, cwd)) : null;
|
|
57
|
+
if (moved && moved !== target.path && (await readTextFileIfExists(moved)).includes(`"${SUMMER_MCP_SERVER_NAME}"`)) {
|
|
58
|
+
target.warnings.push(`${moved} still lists ${SUMMER_MCP_SERVER_NAME} from an earlier setup. ${spec.label} now reads ${target.path}, so setup writes it there; the old entry stays for older builds.`);
|
|
59
|
+
}
|
|
55
60
|
const snippet = renderConfigSnippet(options.agent, server);
|
|
56
61
|
const dryRun = Boolean(options.dryRun);
|
|
57
62
|
const print = Boolean(options.print);
|
|
@@ -93,6 +93,8 @@ export interface AgentSpec {
|
|
|
93
93
|
project: ((ctx: PathContext) => string) | null;
|
|
94
94
|
/** Extra warning printed when project scope is requested (also when it is honoured). */
|
|
95
95
|
projectNote?: string;
|
|
96
|
+
/** The user file the agent read before it moved: setup names an earlier summer-engine entry left there. */
|
|
97
|
+
movedFrom?: (ctx: PathContext) => string;
|
|
96
98
|
};
|
|
97
99
|
/** What to do after the config is written. */
|
|
98
100
|
restart: string;
|
|
@@ -112,6 +114,13 @@ export declare const supportedAgents: readonly SupportedAgent[];
|
|
|
112
114
|
export declare const skillCapableAgents: readonly SupportedAgent[];
|
|
113
115
|
/** Every spelling → canonical id. Aliases must be unique across the table. */
|
|
114
116
|
export declare function agentAliasMap(): Record<string, SupportedAgent>;
|
|
117
|
+
/**
|
|
118
|
+
* Why `summer setup <agent> --cli` cannot set this agent up, or null. --cli
|
|
119
|
+
* installs the skills and no MCP server, so the agent needs a skills folder,
|
|
120
|
+
* and one that takes SKILL.md files: rule files (Roo Code) and the Gemini
|
|
121
|
+
* extension assume the MCP tools.
|
|
122
|
+
*/
|
|
123
|
+
export declare function cliSetupRefusal(spec: AgentSpec): string | null;
|
|
115
124
|
export declare function agentLabel(id: SupportedAgent): string;
|
|
116
125
|
export declare function defaultPathContext(env?: NodeJS.ProcessEnv, cwd?: string): PathContext;
|
|
117
126
|
export interface ResolvedMcpPath {
|
|
@@ -54,7 +54,8 @@ function appData(ctx) {
|
|
|
54
54
|
return ctx.env.APPDATA ?? join(ctx.home, "AppData", "Roaming");
|
|
55
55
|
}
|
|
56
56
|
function xdgConfig(ctx) {
|
|
57
|
-
|
|
57
|
+
// An empty XDG_CONFIG_HOME counts as unset (XDG Base Directory spec).
|
|
58
|
+
return ctx.env.XDG_CONFIG_HOME || join(ctx.home, ".config");
|
|
58
59
|
}
|
|
59
60
|
/** ~/.config on Linux, %APPDATA% on Windows, ~/Library/Application Support on macOS. */
|
|
60
61
|
function appConfigRoot(ctx) {
|
|
@@ -183,8 +184,10 @@ const SPECS = [
|
|
|
183
184
|
signIn: (name) => `Devin Desktop opens summer.games in your browser the first time ${name} connects; sign in there.`,
|
|
184
185
|
},
|
|
185
186
|
mcp: {
|
|
186
|
-
|
|
187
|
+
// docs.devin.ai/desktop/cascade/mcp (2026-10-16): ~/.config/devin (or $XDG_CONFIG_HOME/devin), %APPDATA%\devin on Windows.
|
|
188
|
+
user: (ctx) => join(xdgOrAppData(ctx), "devin", "mcp_config.json"),
|
|
187
189
|
project: null,
|
|
190
|
+
movedFrom: home(".codeium", "windsurf", "mcp_config.json"),
|
|
188
191
|
},
|
|
189
192
|
restart: "Restart Devin Desktop (formerly Windsurf) and refresh MCP servers from the agent settings. Devin's docs say mcp_config.json configures the Cascade agent; for the Devin agent add the server in the app's MCP settings with the same command.",
|
|
190
193
|
skills: sharedSkills({ legacyDirs: { user: home(".codeium", "windsurf", "skills"), project: project(".windsurf", "skills") } }),
|
|
@@ -685,6 +688,24 @@ export function agentAliasMap() {
|
|
|
685
688
|
}
|
|
686
689
|
return map;
|
|
687
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* Why `summer setup <agent> --cli` cannot set this agent up, or null. --cli
|
|
693
|
+
* installs the skills and no MCP server, so the agent needs a skills folder,
|
|
694
|
+
* and one that takes SKILL.md files: rule files (Roo Code) and the Gemini
|
|
695
|
+
* extension assume the MCP tools.
|
|
696
|
+
*/
|
|
697
|
+
export function cliSetupRefusal(spec) {
|
|
698
|
+
if (!spec.skills) {
|
|
699
|
+
return `${spec.label} has no skills folder, so --cli has nothing to install. Run summer setup ${spec.id} without --cli for the MCP server.`;
|
|
700
|
+
}
|
|
701
|
+
if (spec.format === "json-gemini") {
|
|
702
|
+
return `Gemini CLI loads Summer's skills only through its extension, which runs the MCP server. Run summer setup ${spec.id} without --cli, or use antigravity.`;
|
|
703
|
+
}
|
|
704
|
+
if (spec.skills.kind !== "skill-dir" && spec.skills.kind !== "opencode-skill-dir") {
|
|
705
|
+
return `${spec.label} gets Summer's skills as rule files that tell it to use the MCP tools. Run summer setup ${spec.id} without --cli.`;
|
|
706
|
+
}
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
688
709
|
export function agentLabel(id) {
|
|
689
710
|
return agentSpec(id).label;
|
|
690
711
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PathContext, SupportedAgent } from "./agent-table.js";
|
|
2
|
+
/**
|
|
3
|
+
* Allow rules for the CLI path, added only with `summer setup <agent> --cli
|
|
4
|
+
* --allow-rules --yes` (the flag is in the command the person approves), so
|
|
5
|
+
* the agent's read-only and dry-run summer commands do not each ask:
|
|
6
|
+
*
|
|
7
|
+
* - the reads in AUTO_ALLOWED_READS, as they are. The list is reviewed by
|
|
8
|
+
* hand: a new read operation in the catalog does not join it, and
|
|
9
|
+
* cli-permissions.test.ts fails until someone adds it on purpose;
|
|
10
|
+
* - every spend (generation and the rest) only as a dry run, `--dry-run`
|
|
11
|
+
* right after the action. A call with that flag is a dry run whatever else
|
|
12
|
+
* it says (parseCallFlags), so the rule cannot let a real spend through.
|
|
13
|
+
*
|
|
14
|
+
* Changes, publishing, prices, deletes and messages keep asking. Claude Code
|
|
15
|
+
* matches the command text (`Bash(summer jobs wait *)` matches the bare
|
|
16
|
+
* command and `summer jobs wait --job-id x`, not `summer jobs waitx`); Codex
|
|
17
|
+
* matches argument lists, in a rules file of Summer's own. Both get the
|
|
18
|
+
* `summer` and the `npx -y summer-engine@latest` forms.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Reads that run without asking. Each one reads Summer data or the open
|
|
22
|
+
* editor's state; `jobs wait` and `assets get` also save the files a finished
|
|
23
|
+
* job names (media files only, never over an existing file). Left out on
|
|
24
|
+
* purpose: `engine capture-gameplay` (it starts Summer Engine on a project, so
|
|
25
|
+
* the project's own code runs, and writes to any --out folder) and
|
|
26
|
+
* `studio open` (it opens a page in the browser).
|
|
27
|
+
*/
|
|
28
|
+
export declare const AUTO_ALLOWED_READS: readonly string[];
|
|
29
|
+
export type CliCommand = {
|
|
30
|
+
words: string[];
|
|
31
|
+
dryRun: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** The commands allowed without asking: `summer status`, the reviewed reads, and a dry run of every spend in the catalog. */
|
|
34
|
+
export declare function allowedCliCommands(): CliCommand[];
|
|
35
|
+
export declare const LAUNCHERS: ReadonlyArray<readonly string[]>;
|
|
36
|
+
/** Claude Code `permissions.allow` rules, for both launchers. */
|
|
37
|
+
export declare function claudeAllowRules(): string[];
|
|
38
|
+
/** Codex rules: one prefix_rule per launcher, area and kind, the actions as a union. */
|
|
39
|
+
export declare function codexRules(): string;
|
|
40
|
+
export interface AllowRulesResult {
|
|
41
|
+
agent: SupportedAgent;
|
|
42
|
+
path: string;
|
|
43
|
+
/** Every rule Summer wants there (Codex: its patterns); empty for a removal. */
|
|
44
|
+
rules: string[];
|
|
45
|
+
/** What this run added, or removed with --remove-allow-rules. */
|
|
46
|
+
changed: string[];
|
|
47
|
+
wrote: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** The agents with clean per-command allow rules: Claude Code (settings.json) and Codex (rules files). */
|
|
50
|
+
export declare function supportsAllowRules(agent: SupportedAgent): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Where the rules go. Claude Code: the user's settings, or with --scope
|
|
53
|
+
* project the project's personal settings.local.json (never the shared file).
|
|
54
|
+
* Codex: always the user's rules folder; a project's .codex/rules is shared
|
|
55
|
+
* through git, so Summer does not write there.
|
|
56
|
+
*/
|
|
57
|
+
export declare function allowRulesPath(agent: SupportedAgent, scope: "user" | "project", ctx: PathContext): string;
|
|
58
|
+
/**
|
|
59
|
+
* Add the allow rules. Claude Code: merged into `permissions.allow`, nothing
|
|
60
|
+
* removed, every other setting kept. Codex: Summer's own rules file,
|
|
61
|
+
* rewritten. Writes nothing unless `write`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function addAllowRules(agent: SupportedAgent, path: string, write: boolean): Promise<AllowRulesResult>;
|
|
64
|
+
/** Remove exactly the rules Summer adds (Claude Code), or Summer's own rules file (Codex). Writes nothing unless `write`. */
|
|
65
|
+
export declare function removeAllowRules(agent: SupportedAgent, path: string, write: boolean): Promise<AllowRulesResult>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { chmod, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { CLI_AREAS } from "../core/ops/areas.js";
|
|
5
|
+
import { localEntries } from "../core/ops/catalog.js";
|
|
6
|
+
import { shippedCloudEntries } from "../core/ops/public-catalog.js";
|
|
7
|
+
/**
|
|
8
|
+
* Allow rules for the CLI path, added only with `summer setup <agent> --cli
|
|
9
|
+
* --allow-rules --yes` (the flag is in the command the person approves), so
|
|
10
|
+
* the agent's read-only and dry-run summer commands do not each ask:
|
|
11
|
+
*
|
|
12
|
+
* - the reads in AUTO_ALLOWED_READS, as they are. The list is reviewed by
|
|
13
|
+
* hand: a new read operation in the catalog does not join it, and
|
|
14
|
+
* cli-permissions.test.ts fails until someone adds it on purpose;
|
|
15
|
+
* - every spend (generation and the rest) only as a dry run, `--dry-run`
|
|
16
|
+
* right after the action. A call with that flag is a dry run whatever else
|
|
17
|
+
* it says (parseCallFlags), so the rule cannot let a real spend through.
|
|
18
|
+
*
|
|
19
|
+
* Changes, publishing, prices, deletes and messages keep asking. Claude Code
|
|
20
|
+
* matches the command text (`Bash(summer jobs wait *)` matches the bare
|
|
21
|
+
* command and `summer jobs wait --job-id x`, not `summer jobs waitx`); Codex
|
|
22
|
+
* matches argument lists, in a rules file of Summer's own. Both get the
|
|
23
|
+
* `summer` and the `npx -y summer-engine@latest` forms.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Reads that run without asking. Each one reads Summer data or the open
|
|
27
|
+
* editor's state; `jobs wait` and `assets get` also save the files a finished
|
|
28
|
+
* job names (media files only, never over an existing file). Left out on
|
|
29
|
+
* purpose: `engine capture-gameplay` (it starts Summer Engine on a project, so
|
|
30
|
+
* the project's own code runs, and writes to any --out folder) and
|
|
31
|
+
* `studio open` (it opens a page in the browser).
|
|
32
|
+
*/
|
|
33
|
+
export const AUTO_ALLOWED_READS = [
|
|
34
|
+
"whoami", "ops.search", "ops.describe",
|
|
35
|
+
"docs.search", "docs.read", "docs.engine_api", "docs.plan",
|
|
36
|
+
"jobs.get", "jobs.list", "jobs.wait",
|
|
37
|
+
"account.permissions", "profile.get", "team.list", "chats.list", "plugins.search",
|
|
38
|
+
"games.get", "games.list", "games.members", "jams.list", "pricing.get",
|
|
39
|
+
"store.get_listing", "store.status", "experiments.list", "experiments.results",
|
|
40
|
+
"analytics.event_limit", "analytics.events", "analytics.funnel", "analytics.overview", "analytics.summary", "analytics.trend",
|
|
41
|
+
"assets.choices", "assets.get", "assets.get_icon_collection", "assets.get_pack", "assets.list_comments", "assets.list_folders", "assets.list_icon_collections", "assets.search",
|
|
42
|
+
"apps.events", "apps.get", "apps.guide", "apps.list", "apps.page", "apps.read_data", "apps.screenshot", "apps.tools",
|
|
43
|
+
"plans.changes", "plans.chat", "plans.directions", "plans.get", "plans.links", "plans.list", "plans.presence", "plans.show",
|
|
44
|
+
"projects.builds", "projects.get", "projects.list", "projects.open_in_engine", "projects.preview_build",
|
|
45
|
+
"stories.get", "stories.list", "studio.map", "studio.page", "studio.read_form",
|
|
46
|
+
"marketing.applications", "marketing.content_feed", "marketing.content_videos", "marketing.get_brand_pack", "marketing.list_content_games",
|
|
47
|
+
"engine.debug_views", "engine.game_probe", "engine.get_console", "engine.get_debugger_errors", "engine.get_debugger_warnings", "engine.get_diagnostics",
|
|
48
|
+
"engine.get_project_context", "engine.get_runtime_tree", "engine.get_scene_tree", "engine.get_script_errors", "engine.grep", "engine.inspect_asset",
|
|
49
|
+
"engine.inspect_node", "engine.inspect_resource", "engine.inspect_runtime_node", "engine.is_running", "engine.list_camera_bookmarks", "engine.list_ui_actions",
|
|
50
|
+
"engine.measure", "engine.navigation_probe", "engine.raycast", "engine.read_file", "engine.recent_events", "engine.scene_audit", "engine.screenshot",
|
|
51
|
+
"engine.shot_sheet", "engine.snapshot_diff", "engine.starcast", "engine.test_placement", "engine.ui_screenshot", "engine.ui_tree", "engine.wait_for_event",
|
|
52
|
+
"engine.world_snapshot", "engine.zoom",
|
|
53
|
+
];
|
|
54
|
+
/** The shell words of a command, after the launcher: `assets.generate_image` -> `assets generate-image`. */
|
|
55
|
+
const wordsOf = (op) => (op.includes(".") ? [op.split(".")[0], op.split(".")[1].replace(/_/g, "-")] : [op]);
|
|
56
|
+
/** The commands allowed without asking: `summer status`, the reviewed reads, and a dry run of every spend in the catalog. */
|
|
57
|
+
export function allowedCliCommands() {
|
|
58
|
+
const known = new Map([...localEntries(), ...shippedCloudEntries()].map((entry) => [entry.name, entry]));
|
|
59
|
+
const reads = AUTO_ALLOWED_READS.filter((op) => known.get(op)?.effect === "read").map((op) => ({ words: wordsOf(op), dryRun: false }));
|
|
60
|
+
const areas = new Set(CLI_AREAS);
|
|
61
|
+
const spends = [...known.values()]
|
|
62
|
+
.filter((entry) => entry.effect === "spend" && entry.status !== "deprecated" && areas.has(entry.name.split(".")[0]))
|
|
63
|
+
.map((entry) => ({ words: wordsOf(entry.name), dryRun: true }));
|
|
64
|
+
return [{ words: ["status"], dryRun: false }, ...reads, ...spends].sort((a, b) => a.words.join(" ").localeCompare(b.words.join(" ")) || Number(a.dryRun) - Number(b.dryRun));
|
|
65
|
+
}
|
|
66
|
+
export const LAUNCHERS = [["summer"], ["npx", "-y", "summer-engine@latest"]];
|
|
67
|
+
/** Claude Code `permissions.allow` rules, for both launchers. */
|
|
68
|
+
export function claudeAllowRules() {
|
|
69
|
+
return LAUNCHERS.flatMap((launcher) => allowedCliCommands().map(({ words, dryRun }) => `Bash(${[...launcher, ...words, ...(dryRun ? ["--dry-run"] : [])].join(" ")} *)`));
|
|
70
|
+
}
|
|
71
|
+
const starlark = (value) => (typeof value === "string" ? JSON.stringify(value) : `[${value.map((item) => JSON.stringify(item)).join(", ")}]`);
|
|
72
|
+
const CODEX_HEADER = "# Written by summer setup codex --cli --allow-rules; setup rewrites this file and --remove-allow-rules deletes it.";
|
|
73
|
+
/** Codex rules: one prefix_rule per launcher, area and kind, the actions as a union. */
|
|
74
|
+
export function codexRules() {
|
|
75
|
+
const groups = new Map();
|
|
76
|
+
for (const launcher of LAUNCHERS) {
|
|
77
|
+
for (const { words, dryRun } of allowedCliCommands()) {
|
|
78
|
+
const head = words.length > 1 ? words.slice(0, 1) : [];
|
|
79
|
+
const key = `${launcher.join(" ")}|${head.join(" ")}|${dryRun}`;
|
|
80
|
+
const group = groups.get(key) ?? { launcher, head, actions: [], dryRun };
|
|
81
|
+
group.actions.push(words[words.length - 1]);
|
|
82
|
+
groups.set(key, group);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const rules = [...groups.values()].map(({ launcher, head, actions, dryRun }) => {
|
|
86
|
+
const pattern = [...launcher.map(starlark), ...head.map(starlark), starlark(actions.length === 1 ? actions[0] : actions), ...(dryRun ? [starlark("--dry-run")] : [])];
|
|
87
|
+
const example = [...launcher, ...head, actions[0], ...(dryRun ? ["--dry-run"] : [])].join(" ");
|
|
88
|
+
const why = dryRun ? "Summer dry run: checks the input, spends nothing" : "Summer read: changes no Summer data; jobs wait and assets get save the files they name";
|
|
89
|
+
return `prefix_rule(\n pattern = [${pattern.join(", ")}],\n decision = "allow",\n justification = ${starlark(why)},\n match = [${starlark(example)}],\n)`;
|
|
90
|
+
});
|
|
91
|
+
return `${CODEX_HEADER}\n# Reads, and spends only as dry runs. Changes, publishing, deletes and messages still ask.\n\n${rules.join("\n\n")}\n`;
|
|
92
|
+
}
|
|
93
|
+
/** The agents with clean per-command allow rules: Claude Code (settings.json) and Codex (rules files). */
|
|
94
|
+
export function supportsAllowRules(agent) {
|
|
95
|
+
return agent === "claude-code" || agent === "codex";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Where the rules go. Claude Code: the user's settings, or with --scope
|
|
99
|
+
* project the project's personal settings.local.json (never the shared file).
|
|
100
|
+
* Codex: always the user's rules folder; a project's .codex/rules is shared
|
|
101
|
+
* through git, so Summer does not write there.
|
|
102
|
+
*/
|
|
103
|
+
export function allowRulesPath(agent, scope, ctx) {
|
|
104
|
+
if (agent === "codex")
|
|
105
|
+
return ctx.env.SUMMER_CODEX_RULES_FILE || join(ctx.env.CODEX_HOME || join(ctx.home, ".codex"), "rules", "summer.rules");
|
|
106
|
+
return ctx.env.SUMMER_CLAUDE_SETTINGS_FILE || (scope === "project" ? join(ctx.cwd, ".claude", "settings.local.json") : join(ctx.home, ".claude", "settings.json"));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Write next to the file, then rename over it, so Claude Code never reads
|
|
110
|
+
* half a file. A symlinked settings file (a dotfiles repo) is written through
|
|
111
|
+
* the link, in its real folder, and keeps its mode.
|
|
112
|
+
*/
|
|
113
|
+
async function writeAtomically(path, text) {
|
|
114
|
+
const target = existsSync(path) ? await realpath(path) : path;
|
|
115
|
+
await mkdir(dirname(target), { recursive: true });
|
|
116
|
+
const mode = existsSync(target) ? (await stat(target)).mode & 0o7777 : undefined;
|
|
117
|
+
const temp = `${target}.summer-${process.pid}.tmp`;
|
|
118
|
+
await writeFile(temp, text, mode === undefined ? {} : { mode });
|
|
119
|
+
if (mode !== undefined)
|
|
120
|
+
await chmod(temp, mode);
|
|
121
|
+
await rename(temp, target);
|
|
122
|
+
}
|
|
123
|
+
async function readClaudeSettings(path) {
|
|
124
|
+
let settings = {};
|
|
125
|
+
if (existsSync(path)) {
|
|
126
|
+
try {
|
|
127
|
+
settings = JSON.parse(await readFile(path, "utf8"));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error(`${path} is not valid JSON, so Summer left its allow rules alone. Fix the file, then run setup again.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
134
|
+
throw new Error(`${path} is not a JSON object, so Summer left its allow rules alone.`);
|
|
135
|
+
const record = settings;
|
|
136
|
+
const permissions = record.permissions ?? {};
|
|
137
|
+
if (!permissions || typeof permissions !== "object" || Array.isArray(permissions))
|
|
138
|
+
throw new Error(`${path}: permissions is not an object, so Summer left its allow rules alone.`);
|
|
139
|
+
const allow = permissions.allow ?? [];
|
|
140
|
+
if (!Array.isArray(allow))
|
|
141
|
+
throw new Error(`${path}: permissions.allow is not a list, so Summer left its allow rules alone.`);
|
|
142
|
+
return { settings: record, permissions: permissions, allow };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Add the allow rules. Claude Code: merged into `permissions.allow`, nothing
|
|
146
|
+
* removed, every other setting kept. Codex: Summer's own rules file,
|
|
147
|
+
* rewritten. Writes nothing unless `write`.
|
|
148
|
+
*/
|
|
149
|
+
export async function addAllowRules(agent, path, write) {
|
|
150
|
+
if (agent === "codex") {
|
|
151
|
+
const text = codexRules();
|
|
152
|
+
const rules = text.split("\n").filter((line) => line.startsWith(" pattern = ")).map((line) => line.trim());
|
|
153
|
+
const changed = (existsSync(path) ? await readFile(path, "utf8") : "") !== text;
|
|
154
|
+
if (write && changed)
|
|
155
|
+
await writeAtomically(path, text);
|
|
156
|
+
return { agent, path, rules, changed: changed ? rules : [], wrote: write && changed };
|
|
157
|
+
}
|
|
158
|
+
const rules = claudeAllowRules();
|
|
159
|
+
const { settings, permissions, allow } = await readClaudeSettings(path);
|
|
160
|
+
const added = rules.filter((rule) => !allow.includes(rule));
|
|
161
|
+
if (write && added.length > 0)
|
|
162
|
+
await writeAtomically(path, `${JSON.stringify({ ...settings, permissions: { ...permissions, allow: [...allow, ...added] } }, null, 2)}\n`);
|
|
163
|
+
return { agent, path, rules, changed: added, wrote: write && added.length > 0 };
|
|
164
|
+
}
|
|
165
|
+
/** Remove exactly the rules Summer adds (Claude Code), or Summer's own rules file (Codex). Writes nothing unless `write`. */
|
|
166
|
+
export async function removeAllowRules(agent, path, write) {
|
|
167
|
+
if (agent === "codex") {
|
|
168
|
+
const ours = existsSync(path) && (await readFile(path, "utf8")).startsWith("# Written by summer setup codex");
|
|
169
|
+
if (write && ours)
|
|
170
|
+
await rm(path);
|
|
171
|
+
return { agent, path, rules: [], changed: ours ? [path] : [], wrote: write && ours };
|
|
172
|
+
}
|
|
173
|
+
if (!existsSync(path))
|
|
174
|
+
return { agent, path, rules: [], changed: [], wrote: false };
|
|
175
|
+
const ours = new Set(claudeAllowRules());
|
|
176
|
+
const { settings, permissions, allow } = await readClaudeSettings(path);
|
|
177
|
+
const removed = allow.filter((rule) => typeof rule === "string" && ours.has(rule));
|
|
178
|
+
if (write && removed.length > 0) {
|
|
179
|
+
await writeAtomically(path, `${JSON.stringify({ ...settings, permissions: { ...permissions, allow: allow.filter((rule) => !removed.includes(rule)) } }, null, 2)}\n`);
|
|
180
|
+
}
|
|
181
|
+
return { agent, path, rules: [], changed: removed, wrote: write && removed.length > 0 };
|
|
182
|
+
}
|
|
@@ -38,6 +38,8 @@ export interface SkillSetupOptions {
|
|
|
38
38
|
stableOnly?: boolean;
|
|
39
39
|
/** Install only the asset skills (`setup --assets-only`). */
|
|
40
40
|
assetsOnly?: boolean;
|
|
41
|
+
/** false: no Claude Code slash commands (`setup --cli`; they start work through the MCP tools). */
|
|
42
|
+
commands?: boolean;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* The preview clause of the setup summary — one wording for the result message
|
package/dist/installer/setup.js
CHANGED
|
@@ -44,6 +44,7 @@ export function setupSkills(agent, options) {
|
|
|
44
44
|
recommended,
|
|
45
45
|
stableOnly,
|
|
46
46
|
assetsOnly,
|
|
47
|
+
commands: options.commands !== false,
|
|
47
48
|
});
|
|
48
49
|
if (!invocation) {
|
|
49
50
|
return {
|
|
@@ -145,6 +146,8 @@ function skillInstallInvocation(agent, opts) {
|
|
|
145
146
|
baseArgs.push("--force");
|
|
146
147
|
if (opts.stableOnly)
|
|
147
148
|
baseArgs.push("--stable-only");
|
|
149
|
+
if (!opts.commands)
|
|
150
|
+
baseArgs.push("--no-commands");
|
|
148
151
|
return {
|
|
149
152
|
command,
|
|
150
153
|
args: [...prefix, ...baseArgs],
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type SkillRegistryEntry } from "../core/skills-registry.js";
|
|
2
|
+
export interface RefreshedFolder {
|
|
3
|
+
dir: string;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
/** Skills replaced with this version's copy. */
|
|
7
|
+
updated: string[];
|
|
8
|
+
/** New library skills added to a full install. */
|
|
9
|
+
added: string[];
|
|
10
|
+
/** Summer's skills someone changed after install: left as they are. */
|
|
11
|
+
kept: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface RefreshFailure {
|
|
14
|
+
dir: string;
|
|
15
|
+
skill?: string;
|
|
16
|
+
code: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RefreshResult {
|
|
19
|
+
refreshed: RefreshedFolder[];
|
|
20
|
+
failures: RefreshFailure[];
|
|
21
|
+
}
|
|
22
|
+
/** User-scope skill folders with one folder per skill, each listed once. */
|
|
23
|
+
export declare function skillFolderLocations(): string[];
|
|
24
|
+
export declare function refreshInstalledSkills(cliVersion: string, options?: {
|
|
25
|
+
dirs?: string[];
|
|
26
|
+
registry?: readonly SkillRegistryEntry[];
|
|
27
|
+
}): RefreshResult;
|
|
28
|
+
/** The one message a person and their agent see after a refresh, or null when nothing changed. */
|
|
29
|
+
export declare function refreshNotice(refreshed: readonly RefreshedFolder[], failures?: readonly RefreshFailure[]): string | null;
|