summer-engine 4.0.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) 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 +2 -2
  7. package/CHANGELOG.md +48 -0
  8. package/README.md +21 -10
  9. package/dist/cli/command-usage.d.ts +8 -0
  10. package/dist/cli/command-usage.js +22 -0
  11. package/dist/cli/commands/config.js +3 -2
  12. package/dist/cli/commands/create.js +12 -3
  13. package/dist/cli/commands/list.d.ts +5 -0
  14. package/dist/cli/commands/list.js +30 -13
  15. package/dist/cli/commands/login.js +1 -1
  16. package/dist/cli/commands/memory.js +6 -2
  17. package/dist/cli/commands/ops.d.ts +20 -3
  18. package/dist/cli/commands/ops.js +60 -27
  19. package/dist/cli/commands/setup.d.ts +2 -0
  20. package/dist/cli/commands/setup.js +219 -16
  21. package/dist/cli/commands/skills.js +20 -7
  22. package/dist/cli/commands/status.d.ts +26 -0
  23. package/dist/cli/commands/status.js +36 -21
  24. package/dist/cli/commands/tool.js +4 -0
  25. package/dist/cli/index.js +2 -0
  26. package/dist/core/agent-host.d.ts +3 -0
  27. package/dist/core/agent-host.js +8 -2
  28. package/dist/core/capabilities/apps.d.ts +10 -10
  29. package/dist/core/capabilities/audit/args.d.ts +6 -6
  30. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  31. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  32. package/dist/core/capabilities/hdri-import.d.ts +2 -2
  33. package/dist/core/capabilities/motion-generation.d.ts +2 -2
  34. package/dist/core/capabilities/placement.d.ts +23 -23
  35. package/dist/core/capabilities/runtime-control.d.ts +38 -38
  36. package/dist/core/capabilities/seeing/args.d.ts +16 -16
  37. package/dist/core/capabilities/ui-control.d.ts +2 -2
  38. package/dist/core/capabilities/world-snapshot.d.ts +4 -4
  39. package/dist/core/cli-usage-beacon.d.ts +1 -0
  40. package/dist/core/cli-usage-beacon.js +18 -0
  41. package/dist/core/cli-usage.d.ts +49 -0
  42. package/dist/core/cli-usage.js +108 -0
  43. package/dist/core/feedback/client.d.ts +1 -1
  44. package/dist/core/feedback/client.js +3 -2
  45. package/dist/core/hosted-mcp.d.ts +7 -1
  46. package/dist/core/hosted-mcp.js +6 -4
  47. package/dist/core/library-search.d.ts +2 -2
  48. package/dist/core/ops/areas.d.ts +11 -0
  49. package/dist/core/ops/areas.js +24 -0
  50. package/dist/core/ops/catalog.d.ts +11 -0
  51. package/dist/core/ops/catalog.js +24 -1
  52. package/dist/core/ops/cloud.d.ts +10 -2
  53. package/dist/core/ops/cloud.js +51 -28
  54. package/dist/core/ops/contract.d.ts +2 -1
  55. package/dist/core/ops/contract.js +2 -10
  56. package/dist/core/ops/public-catalog.d.ts +48 -0
  57. package/dist/core/ops/public-catalog.js +56 -0
  58. package/dist/core/ops/search.d.ts +0 -1
  59. package/dist/core/ops/search.js +169 -37
  60. package/dist/core/telemetry.d.ts +10 -0
  61. package/dist/core/telemetry.js +58 -29
  62. package/dist/installer/agent-config.js +5 -0
  63. package/dist/installer/agent-table.d.ts +9 -0
  64. package/dist/installer/agent-table.js +23 -2
  65. package/dist/installer/cli-permissions.d.ts +65 -0
  66. package/dist/installer/cli-permissions.js +182 -0
  67. package/dist/installer/setup.d.ts +2 -0
  68. package/dist/installer/setup.js +3 -0
  69. package/dist/installer/skills-refresh.d.ts +29 -0
  70. package/dist/installer/skills-refresh.js +261 -0
  71. package/dist/installer/version-check.d.ts +22 -2
  72. package/dist/installer/version-check.js +66 -9
  73. package/dist/mcp/ops-surface.js +16 -6
  74. package/dist/mcp/server.d.ts +10 -0
  75. package/dist/mcp/server.js +38 -8
  76. package/dist/project-memory/agent-instructions.d.ts +47 -0
  77. package/dist/project-memory/agent-instructions.js +226 -0
  78. package/gemini-extension.json +3 -2
  79. package/integrations/README.md +1 -1
  80. package/integrations/claude/README.md +6 -0
  81. package/integrations/claude/manifest-target.json +4 -0
  82. package/integrations/gemini/README.md +11 -0
  83. package/integrations/windsurf/README.md +1 -1
  84. package/library/skills/3d-lighting/SKILL.md +16 -0
  85. package/library/skills/README.md +5 -4
  86. package/library/skills/adaptive-music/SKILL.md +21 -0
  87. package/library/skills/agent-playtesting/SKILL.md +22 -0
  88. package/library/skills/ai-characters-in-games/SKILL.md +15 -0
  89. package/library/skills/ambient-bed/SKILL.md +16 -0
  90. package/library/skills/animated-loop/SKILL.md +19 -0
  91. package/library/skills/animation-tree/SKILL.md +26 -0
  92. package/library/skills/art-direction/SKILL.md +14 -0
  93. package/library/skills/asset-strategy/SKILL.md +30 -0
  94. package/library/skills/audio-direction/SKILL.md +16 -0
  95. package/library/skills/auto-fire-targeting/SKILL.md +14 -0
  96. package/library/skills/brainstorm-game/SKILL.md +13 -0
  97. package/library/skills/browse-templates/SKILL.md +10 -0
  98. package/library/skills/camera-rigs/SKILL.md +20 -0
  99. package/library/skills/character-animation-wiring/SKILL.md +24 -0
  100. package/library/skills/character-model/SKILL.md +28 -0
  101. package/library/skills/character-movement/SKILL.md +24 -0
  102. package/library/skills/character-portrait/SKILL.md +15 -0
  103. package/library/skills/cinematic-cutscene/SKILL.md +21 -0
  104. package/library/skills/combat-basics/SKILL.md +19 -0
  105. package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
  106. package/library/skills/concept-art/SKILL.md +13 -0
  107. package/library/skills/concept-to-level/SKILL.md +20 -1
  108. package/library/skills/concept-to-level/resource.yaml +2 -2
  109. package/library/skills/create-asset-sheet/SKILL.md +18 -0
  110. package/library/skills/debug/SKILL.md +32 -0
  111. package/library/skills/debugging-game-feel/SKILL.md +12 -0
  112. package/library/skills/design-level/SKILL.md +17 -0
  113. package/library/skills/design-mechanic/SKILL.md +17 -0
  114. package/library/skills/design-npc/SKILL.md +21 -0
  115. package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
  116. package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
  117. package/library/skills/environment-kit/SKILL.md +23 -0
  118. package/library/skills/export-and-ship/SKILL.md +23 -0
  119. package/library/skills/fabricating-assets/SKILL.md +23 -0
  120. package/library/skills/facial-and-lipsync/SKILL.md +22 -0
  121. package/library/skills/fps-controller/SKILL.md +17 -0
  122. package/library/skills/game-feel/SKILL.md +26 -0
  123. package/library/skills/gameskill/SKILL.md +11 -0
  124. package/library/skills/gdscript-patterns/SKILL.md +15 -0
  125. package/library/skills/generate-motion/SKILL.md +21 -0
  126. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
  127. package/library/skills/grow-analytics/SKILL.md +10 -0
  128. package/library/skills/headless-scripting/SKILL.md +13 -0
  129. package/library/skills/in-game-purchases/SKILL.md +11 -0
  130. package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
  131. package/library/skills/make-game/SKILL.md +29 -0
  132. package/library/skills/multiplayer/SKILL.md +12 -0
  133. package/library/skills/multiplayer-movement/SKILL.md +18 -0
  134. package/library/skills/multiplayer-project/SKILL.md +17 -0
  135. package/library/skills/multiplayer-publish/SKILL.md +17 -5
  136. package/library/skills/multiplayer-state/SKILL.md +16 -0
  137. package/library/skills/multiplayer-testing/SKILL.md +15 -0
  138. package/library/skills/music-track/SKILL.md +16 -0
  139. package/library/skills/navigate-summer/SKILL.md +15 -0
  140. package/library/skills/new-project/SKILL.md +11 -0
  141. package/library/skills/organic-model/SKILL.md +24 -0
  142. package/library/skills/pixel-art/SKILL.md +15 -0
  143. package/library/skills/play/SKILL.md +18 -0
  144. package/library/skills/playtesting-a-feature/SKILL.md +23 -0
  145. package/library/skills/procedural-animation/SKILL.md +18 -0
  146. package/library/skills/prop-model/SKILL.md +23 -0
  147. package/library/skills/publish-your-game/SKILL.md +27 -0
  148. package/library/skills/release-readiness/SKILL.md +12 -0
  149. package/library/skills/remote-deploy/SKILL.md +14 -0
  150. package/library/skills/retarget/SKILL.md +20 -0
  151. package/library/skills/running-in-the-cloud/SKILL.md +10 -0
  152. package/library/skills/save-load/SKILL.md +15 -0
  153. package/library/skills/scene-composition/SKILL.md +17 -0
  154. package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
  155. package/library/skills/scene-scripting/SKILL.md +30 -0
  156. package/library/skills/scene-to-level/SKILL.md +22 -0
  157. package/library/skills/skill-test/SKILL.md +10 -0
  158. package/library/skills/skybox-panorama/SKILL.md +20 -0
  159. package/library/skills/sound-effect/SKILL.md +23 -0
  160. package/library/skills/spatial-placement/SKILL.md +30 -0
  161. package/library/skills/sprite-sheet/SKILL.md +23 -0
  162. package/library/skills/store-art/SKILL.md +11 -0
  163. package/library/skills/store-listing/SKILL.md +10 -0
  164. package/library/skills/summer-analytics/SKILL.md +15 -0
  165. package/library/skills/summer-apps/SKILL.md +29 -0
  166. package/library/skills/summer-assets/SKILL.md +31 -1
  167. package/library/skills/summer-assets/resource.yaml +2 -2
  168. package/library/skills/summer-cli/SKILL.md +133 -0
  169. package/library/skills/summer-cli/resource.yaml +31 -0
  170. package/library/skills/summer-friends/SKILL.md +14 -0
  171. package/library/skills/summer-leaderboards/SKILL.md +16 -0
  172. package/library/skills/summer-match-results/SKILL.md +17 -0
  173. package/library/skills/summer-matchmaking/SKILL.md +17 -0
  174. package/library/skills/summer-parties/SKILL.md +14 -0
  175. package/library/skills/summer-player-data/SKILL.md +14 -0
  176. package/library/skills/summer-store/SKILL.md +15 -0
  177. package/library/skills/summer-world-chat/SKILL.md +14 -0
  178. package/library/skills/summer-world-saves/SKILL.md +14 -0
  179. package/library/skills/text-to-motion/SKILL.md +15 -0
  180. package/library/skills/tileable-texture/SKILL.md +18 -0
  181. package/library/skills/trailer-shot/SKILL.md +18 -0
  182. package/library/skills/tune-performance/SKILL.md +26 -0
  183. package/library/skills/ui-basics/SKILL.md +14 -0
  184. package/library/skills/ui-graphics/SKILL.md +17 -0
  185. package/library/skills/use-widget-asset/SKILL.md +14 -0
  186. package/library/skills/using-summer/SKILL.md +26 -0
  187. package/library/skills/using-summer/resource.yaml +2 -1
  188. package/library/skills/vehicle-model/SKILL.md +27 -0
  189. package/library/skills/verifying-scenes/SKILL.md +29 -0
  190. package/library/skills/vfx-dissolve/SKILL.md +19 -0
  191. package/library/skills/vfx-fire/SKILL.md +26 -0
  192. package/library/skills/vfx-hit-spark/SKILL.md +23 -0
  193. package/library/skills/vfx-lightning/SKILL.md +23 -0
  194. package/library/skills/vfx-magic-glow/SKILL.md +23 -0
  195. package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
  196. package/library/skills/vfx-smoke/SKILL.md +26 -0
  197. package/library/skills/vfx-water-ripple/SKILL.md +23 -0
  198. package/library/skills/voice-line/SKILL.md +16 -0
  199. package/library/skills/web-and-touch-input/SKILL.md +19 -0
  200. package/library/skills/web-compatibility-look/SKILL.md +20 -0
  201. package/library/skills/world-building-3d/SKILL.md +28 -0
  202. package/package.json +3 -2
  203. package/registry/generated/counts.json +2 -2
  204. package/registry/generated/gemini-extension.json +3 -2
  205. package/registry/generated/index.json +157 -118
  206. package/registry/generated/marketplace.claude.json +2 -2
  207. package/registry/generated/plugin.claude-directory.json +168 -0
  208. package/registry/generated/plugin.claude.json +3 -2
  209. package/registry/generated/plugin.codex.json +15 -11
  210. package/registry/generated/plugin.cursor.json +5 -3
  211. package/registry/generated/plugin.factory.json +3 -2
  212. package/registry/generated/server.json +11 -2
  213. package/registry/generated/skills-index.md +5 -4
  214. package/registry/generated/skills-registry.json +16 -2
  215. package/registry/operations/legacy-names.json +35 -0
  216. 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)
@@ -36,9 +36,19 @@ export interface LocalToolCall {
36
36
  surfaceVersion?: "v3" | "v4";
37
37
  /** The MCP profile: full, engine, assets or custom. */
38
38
  profile?: string;
39
+ /** A failed call's error code (toolErrorCode); never sent for a call that worked. */
40
+ error?: string | null;
39
41
  }
42
+ /**
43
+ * The code a failed tool result names, read from its first text block: the
44
+ * one answer shape's `error.code`, the 3.x `code`, then an engine
45
+ * `failure_reason`. Codes only, never the message or anything else.
46
+ */
47
+ export declare function toolErrorCode(result: unknown, fallback?: string): string | null;
40
48
  /** Report one local tool call: tool name, success, duration, host app name. */
41
49
  export declare function recordLocalToolCall(call: LocalToolCall): void;
50
+ /** POST one usage line with the account token; resolves when sent or given up. Never throws. */
51
+ export declare function postUsage(body: Record<string, unknown>, timeoutMs?: number): Promise<void>;
42
52
  /**
43
53
  * Reset the once-per-session guard. Test-only.
44
54
  */
@@ -36,6 +36,33 @@ export function recordMcpSession() {
36
36
  const mcpClient = currentMcpClient()?.name;
37
37
  send({ client: "summer-cli", event: "mcp_session_start", ...(mcpClient ? { mcpClient: mcpClient.slice(0, 64) } : {}) });
38
38
  }
39
+ const ERROR_CODE = /^[a-z][a-z0-9_]{0,63}$/;
40
+ /**
41
+ * The code a failed tool result names, read from its first text block: the
42
+ * one answer shape's `error.code`, the 3.x `code`, then an engine
43
+ * `failure_reason`. Codes only, never the message or anything else.
44
+ */
45
+ export function toolErrorCode(result, fallback) {
46
+ const content = result?.content;
47
+ const text = Array.isArray(content) ? content[0]?.text : undefined;
48
+ let parsed = null;
49
+ if (typeof text === "string" && text.length < 200_000) {
50
+ try {
51
+ const value = JSON.parse(text);
52
+ if (value && typeof value === "object" && !Array.isArray(value))
53
+ parsed = value;
54
+ }
55
+ catch {
56
+ // plain text: no code in it
57
+ }
58
+ }
59
+ const nested = parsed?.error && typeof parsed.error === "object" ? parsed.error.code : undefined;
60
+ for (const candidate of [nested, parsed?.code, parsed?.failure_reason, fallback]) {
61
+ if (typeof candidate === "string" && ERROR_CODE.test(candidate))
62
+ return candidate;
63
+ }
64
+ return null;
65
+ }
39
66
  /** Report one local tool call: tool name, success, duration, host app name. */
40
67
  export function recordLocalToolCall(call) {
41
68
  if (isTelemetryDisabled() || !TOOL_NAME.test(call.tool))
@@ -49,42 +76,44 @@ export function recordLocalToolCall(call) {
49
76
  ...(call.surfaceVersion ? { surfaceVersion: call.surfaceVersion } : {}),
50
77
  ...(call.profile ? { profile: call.profile } : {}),
51
78
  ok: call.ok,
79
+ ...(!call.ok && call.error && ERROR_CODE.test(call.error) ? { error: call.error } : {}),
52
80
  durationMs: Math.max(0, Math.round(call.durationMs)),
53
81
  });
54
82
  }
55
83
  function send(body) {
56
- // Wrap the entire async pipeline in a void-discarded promise so the caller
57
- // never accidentally awaits us.
58
- void (async () => {
84
+ // Void-discarded so the caller never accidentally awaits us.
85
+ void postUsage(body);
86
+ }
87
+ /** POST one usage line with the account token; resolves when sent or given up. Never throws. */
88
+ export async function postUsage(body, timeoutMs = TIMEOUT_MS) {
89
+ try {
90
+ const token = await getAuthToken();
91
+ if (!token)
92
+ return; // unauthenticated usage is allowed; we just can't attribute it
93
+ // Resolved per call, not at import: the gateway the token belongs to is
94
+ // whatever env/config says NOW (see config.resolveGatewayUrl).
95
+ const gatewayUrl = await resolveGatewayUrl();
96
+ const ctrl = new AbortController();
97
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
59
98
  try {
60
- const token = await getAuthToken();
61
- if (!token)
62
- return; // unauthenticated MCP usage is allowed; we just can't attribute it
63
- // Resolved per call, not at import: the gateway the token belongs to is
64
- // whatever env/config says NOW (see config.resolveGatewayUrl).
65
- const gatewayUrl = await resolveGatewayUrl();
66
- const ctrl = new AbortController();
67
- const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
68
- try {
69
- await fetch(`${gatewayUrl}${TELEMETRY_PATH}`, {
70
- method: "POST",
71
- signal: ctrl.signal,
72
- headers: {
73
- ...summerRequestHeaders(),
74
- Authorization: `Bearer ${token}`,
75
- "Content-Type": "application/json",
76
- },
77
- body: JSON.stringify(body),
78
- });
79
- }
80
- finally {
81
- clearTimeout(timer);
82
- }
99
+ await fetch(`${gatewayUrl}${TELEMETRY_PATH}`, {
100
+ method: "POST",
101
+ signal: ctrl.signal,
102
+ headers: {
103
+ ...summerRequestHeaders(),
104
+ Authorization: `Bearer ${token}`,
105
+ "Content-Type": "application/json",
106
+ },
107
+ body: JSON.stringify(body),
108
+ });
83
109
  }
84
- catch {
85
- // never throws — telemetry is best-effort
110
+ finally {
111
+ clearTimeout(timer);
86
112
  }
87
- })();
113
+ }
114
+ catch {
115
+ // never throws — telemetry is best-effort
116
+ }
88
117
  }
89
118
  /**
90
119
  * Reset the once-per-session guard. Test-only.
@@ -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>;