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