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.
Files changed (199) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +15 -11
  4. package/.cursor-plugin/plugin.json +5 -3
  5. package/.factory-plugin/plugin.json +3 -2
  6. package/AGENTS.md +1 -1
  7. package/CHANGELOG.md +37 -0
  8. package/README.md +20 -9
  9. package/dist/cli/commands/config.js +3 -2
  10. package/dist/cli/commands/create.js +12 -3
  11. package/dist/cli/commands/list.d.ts +5 -0
  12. package/dist/cli/commands/list.js +30 -13
  13. package/dist/cli/commands/memory.js +6 -2
  14. package/dist/cli/commands/ops.d.ts +20 -3
  15. package/dist/cli/commands/ops.js +55 -27
  16. package/dist/cli/commands/setup.d.ts +2 -0
  17. package/dist/cli/commands/setup.js +219 -16
  18. package/dist/cli/commands/skills.js +20 -7
  19. package/dist/cli/commands/status.d.ts +26 -0
  20. package/dist/cli/commands/status.js +36 -21
  21. package/dist/core/capabilities/apps.d.ts +10 -10
  22. package/dist/core/capabilities/audit/args.d.ts +6 -6
  23. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  24. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  25. package/dist/core/capabilities/hdri-import.d.ts +2 -2
  26. package/dist/core/capabilities/motion-generation.d.ts +2 -2
  27. package/dist/core/capabilities/placement.d.ts +23 -23
  28. package/dist/core/capabilities/runtime-control.d.ts +38 -38
  29. package/dist/core/capabilities/seeing/args.d.ts +16 -16
  30. package/dist/core/capabilities/ui-control.d.ts +2 -2
  31. package/dist/core/capabilities/world-snapshot.d.ts +4 -4
  32. package/dist/core/library-search.d.ts +2 -2
  33. package/dist/core/ops/areas.d.ts +11 -0
  34. package/dist/core/ops/areas.js +24 -0
  35. package/dist/core/ops/catalog.d.ts +4 -0
  36. package/dist/core/ops/catalog.js +13 -1
  37. package/dist/core/ops/cloud.d.ts +10 -2
  38. package/dist/core/ops/cloud.js +51 -28
  39. package/dist/core/ops/contract.d.ts +2 -1
  40. package/dist/core/ops/contract.js +2 -10
  41. package/dist/core/ops/public-catalog.d.ts +48 -0
  42. package/dist/core/ops/public-catalog.js +56 -0
  43. package/dist/core/ops/search.d.ts +0 -1
  44. package/dist/core/ops/search.js +169 -37
  45. package/dist/installer/agent-config.js +5 -0
  46. package/dist/installer/agent-table.d.ts +9 -0
  47. package/dist/installer/agent-table.js +23 -2
  48. package/dist/installer/cli-permissions.d.ts +65 -0
  49. package/dist/installer/cli-permissions.js +182 -0
  50. package/dist/installer/setup.d.ts +2 -0
  51. package/dist/installer/setup.js +3 -0
  52. package/dist/installer/skills-refresh.d.ts +29 -0
  53. package/dist/installer/skills-refresh.js +261 -0
  54. package/dist/installer/version-check.d.ts +22 -2
  55. package/dist/installer/version-check.js +66 -9
  56. package/dist/mcp/ops-surface.js +16 -6
  57. package/dist/mcp/server.d.ts +10 -0
  58. package/dist/mcp/server.js +32 -3
  59. package/dist/project-memory/agent-instructions.d.ts +47 -0
  60. package/dist/project-memory/agent-instructions.js +226 -0
  61. package/gemini-extension.json +3 -2
  62. package/integrations/README.md +1 -1
  63. package/integrations/claude/README.md +6 -0
  64. package/integrations/claude/manifest-target.json +4 -0
  65. package/integrations/gemini/README.md +11 -0
  66. package/integrations/windsurf/README.md +1 -1
  67. package/library/skills/3d-lighting/SKILL.md +16 -0
  68. package/library/skills/README.md +5 -4
  69. package/library/skills/adaptive-music/SKILL.md +21 -0
  70. package/library/skills/agent-playtesting/SKILL.md +22 -0
  71. package/library/skills/ai-characters-in-games/SKILL.md +15 -0
  72. package/library/skills/ambient-bed/SKILL.md +16 -0
  73. package/library/skills/animated-loop/SKILL.md +19 -0
  74. package/library/skills/animation-tree/SKILL.md +26 -0
  75. package/library/skills/art-direction/SKILL.md +14 -0
  76. package/library/skills/asset-strategy/SKILL.md +30 -0
  77. package/library/skills/audio-direction/SKILL.md +16 -0
  78. package/library/skills/auto-fire-targeting/SKILL.md +14 -0
  79. package/library/skills/brainstorm-game/SKILL.md +13 -0
  80. package/library/skills/browse-templates/SKILL.md +10 -0
  81. package/library/skills/camera-rigs/SKILL.md +20 -0
  82. package/library/skills/character-animation-wiring/SKILL.md +24 -0
  83. package/library/skills/character-model/SKILL.md +28 -0
  84. package/library/skills/character-movement/SKILL.md +24 -0
  85. package/library/skills/character-portrait/SKILL.md +15 -0
  86. package/library/skills/cinematic-cutscene/SKILL.md +21 -0
  87. package/library/skills/combat-basics/SKILL.md +19 -0
  88. package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
  89. package/library/skills/concept-art/SKILL.md +13 -0
  90. package/library/skills/concept-to-level/SKILL.md +20 -1
  91. package/library/skills/concept-to-level/resource.yaml +2 -2
  92. package/library/skills/create-asset-sheet/SKILL.md +18 -0
  93. package/library/skills/debug/SKILL.md +32 -0
  94. package/library/skills/debugging-game-feel/SKILL.md +12 -0
  95. package/library/skills/design-level/SKILL.md +17 -0
  96. package/library/skills/design-mechanic/SKILL.md +17 -0
  97. package/library/skills/design-npc/SKILL.md +21 -0
  98. package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
  99. package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
  100. package/library/skills/environment-kit/SKILL.md +23 -0
  101. package/library/skills/export-and-ship/SKILL.md +23 -0
  102. package/library/skills/fabricating-assets/SKILL.md +23 -0
  103. package/library/skills/facial-and-lipsync/SKILL.md +22 -0
  104. package/library/skills/fps-controller/SKILL.md +17 -0
  105. package/library/skills/game-feel/SKILL.md +26 -0
  106. package/library/skills/gameskill/SKILL.md +11 -0
  107. package/library/skills/gdscript-patterns/SKILL.md +15 -0
  108. package/library/skills/generate-motion/SKILL.md +21 -0
  109. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
  110. package/library/skills/grow-analytics/SKILL.md +10 -0
  111. package/library/skills/headless-scripting/SKILL.md +13 -0
  112. package/library/skills/in-game-purchases/SKILL.md +11 -0
  113. package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
  114. package/library/skills/make-game/SKILL.md +29 -0
  115. package/library/skills/multiplayer/SKILL.md +12 -0
  116. package/library/skills/multiplayer-movement/SKILL.md +18 -0
  117. package/library/skills/multiplayer-project/SKILL.md +17 -0
  118. package/library/skills/multiplayer-publish/SKILL.md +17 -0
  119. package/library/skills/multiplayer-state/SKILL.md +16 -0
  120. package/library/skills/multiplayer-testing/SKILL.md +15 -0
  121. package/library/skills/music-track/SKILL.md +16 -0
  122. package/library/skills/navigate-summer/SKILL.md +15 -0
  123. package/library/skills/new-project/SKILL.md +11 -0
  124. package/library/skills/organic-model/SKILL.md +24 -0
  125. package/library/skills/pixel-art/SKILL.md +15 -0
  126. package/library/skills/play/SKILL.md +18 -0
  127. package/library/skills/playtesting-a-feature/SKILL.md +23 -0
  128. package/library/skills/procedural-animation/SKILL.md +18 -0
  129. package/library/skills/prop-model/SKILL.md +23 -0
  130. package/library/skills/publish-your-game/SKILL.md +27 -0
  131. package/library/skills/release-readiness/SKILL.md +12 -0
  132. package/library/skills/remote-deploy/SKILL.md +14 -0
  133. package/library/skills/retarget/SKILL.md +20 -0
  134. package/library/skills/running-in-the-cloud/SKILL.md +10 -0
  135. package/library/skills/save-load/SKILL.md +15 -0
  136. package/library/skills/scene-composition/SKILL.md +17 -0
  137. package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
  138. package/library/skills/scene-scripting/SKILL.md +30 -0
  139. package/library/skills/scene-to-level/SKILL.md +22 -0
  140. package/library/skills/skill-test/SKILL.md +10 -0
  141. package/library/skills/skybox-panorama/SKILL.md +20 -0
  142. package/library/skills/sound-effect/SKILL.md +23 -0
  143. package/library/skills/spatial-placement/SKILL.md +30 -0
  144. package/library/skills/sprite-sheet/SKILL.md +23 -0
  145. package/library/skills/store-art/SKILL.md +11 -0
  146. package/library/skills/store-listing/SKILL.md +10 -0
  147. package/library/skills/summer-analytics/SKILL.md +15 -0
  148. package/library/skills/summer-apps/SKILL.md +29 -0
  149. package/library/skills/summer-assets/SKILL.md +31 -1
  150. package/library/skills/summer-assets/resource.yaml +2 -2
  151. package/library/skills/summer-cli/SKILL.md +133 -0
  152. package/library/skills/summer-cli/resource.yaml +31 -0
  153. package/library/skills/summer-friends/SKILL.md +14 -0
  154. package/library/skills/summer-leaderboards/SKILL.md +16 -0
  155. package/library/skills/summer-match-results/SKILL.md +17 -0
  156. package/library/skills/summer-matchmaking/SKILL.md +17 -0
  157. package/library/skills/summer-parties/SKILL.md +14 -0
  158. package/library/skills/summer-player-data/SKILL.md +14 -0
  159. package/library/skills/summer-store/SKILL.md +15 -0
  160. package/library/skills/summer-world-chat/SKILL.md +14 -0
  161. package/library/skills/summer-world-saves/SKILL.md +14 -0
  162. package/library/skills/text-to-motion/SKILL.md +15 -0
  163. package/library/skills/tileable-texture/SKILL.md +18 -0
  164. package/library/skills/trailer-shot/SKILL.md +18 -0
  165. package/library/skills/tune-performance/SKILL.md +26 -0
  166. package/library/skills/ui-basics/SKILL.md +14 -0
  167. package/library/skills/ui-graphics/SKILL.md +17 -0
  168. package/library/skills/use-widget-asset/SKILL.md +14 -0
  169. package/library/skills/using-summer/SKILL.md +26 -0
  170. package/library/skills/using-summer/resource.yaml +2 -1
  171. package/library/skills/vehicle-model/SKILL.md +27 -0
  172. package/library/skills/verifying-scenes/SKILL.md +29 -0
  173. package/library/skills/vfx-dissolve/SKILL.md +19 -0
  174. package/library/skills/vfx-fire/SKILL.md +26 -0
  175. package/library/skills/vfx-hit-spark/SKILL.md +23 -0
  176. package/library/skills/vfx-lightning/SKILL.md +23 -0
  177. package/library/skills/vfx-magic-glow/SKILL.md +23 -0
  178. package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
  179. package/library/skills/vfx-smoke/SKILL.md +26 -0
  180. package/library/skills/vfx-water-ripple/SKILL.md +23 -0
  181. package/library/skills/voice-line/SKILL.md +16 -0
  182. package/library/skills/web-and-touch-input/SKILL.md +19 -0
  183. package/library/skills/web-compatibility-look/SKILL.md +20 -0
  184. package/library/skills/world-building-3d/SKILL.md +28 -0
  185. package/package.json +3 -2
  186. package/registry/generated/counts.json +2 -2
  187. package/registry/generated/gemini-extension.json +3 -2
  188. package/registry/generated/index.json +157 -118
  189. package/registry/generated/marketplace.claude.json +2 -2
  190. package/registry/generated/plugin.claude-directory.json +168 -0
  191. package/registry/generated/plugin.claude.json +3 -2
  192. package/registry/generated/plugin.codex.json +15 -11
  193. package/registry/generated/plugin.cursor.json +5 -3
  194. package/registry/generated/plugin.factory.json +3 -2
  195. package/registry/generated/server.json +11 -2
  196. package/registry/generated/skills-index.md +5 -4
  197. package/registry/generated/skills-registry.json +16 -2
  198. package/registry/operations/legacy-names.json +35 -0
  199. package/registry/operations/public-catalog.json +33160 -0
@@ -1,53 +1,185 @@
1
+ import { stem } from "../registry-search.js";
1
2
  /**
2
- * Plain-words ranking over operations, the same scoring the hosted server
3
- * uses, so one list can hold local and cloud hits.
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(["a", "an", "the", "my", "of", "for", "to", "in", "on", "and", "or", "with", "is", "it", "me", "i", "please", "how", "do", "what"]);
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
- .toLowerCase()
9
- .split(/[^a-z0-9]+/)
10
- .filter((word) => word.length > 1 && !STOP.has(word));
11
- }
12
- const stem = (word) => word.length > 3 && word.endsWith("ies") ? `${word.slice(0, -3)}y` : word.length > 3 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word;
13
- export function scoreEntry(entry, query) {
14
- const terms = words(query);
15
- if (!terms.length)
16
- return 0;
17
- const exact = query.trim().toLowerCase();
18
- if (exact === entry.name || entry.legacy?.some((name) => name.split("#")[0] === exact))
19
- return 1000;
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
- [words(entry.name.replace(/[._]/g, " ")), 6],
22
- [words(entry.title), 4],
23
- [(entry.tags ?? []).flatMap(words), 3],
24
- [(entry.legacy ?? []).flatMap((name) => words(name.replace(/^summer_/, "").replace(/[#_]/g, " "))), 2],
25
- [words(entry.summary), 1],
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
- let total = 0;
28
- for (const term of terms.map(stem)) {
29
- let best = 0;
30
- for (const [fieldWords, weight] of fields) {
31
- for (const word of fieldWords) {
32
- const candidate = stem(word);
33
- if (candidate === term)
34
- best = Math.max(best, weight);
35
- else if (term.length >= 4 && candidate.length >= 3 && (candidate.startsWith(term) || term.startsWith(candidate)))
36
- best = Math.max(best, weight / 2);
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 total;
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
- return entries
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
- .map((entry) => ({ entry, score: scoreEntry(entry, query) }))
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
- return ctx.env.XDG_CONFIG_HOME ?? join(ctx.home, ".config");
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
- user: home(".codeium", "windsurf", "mcp_config.json"),
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
@@ -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;