summer-engine 2.3.0 → 2.5.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 (176) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +20 -5
  3. package/.codex-plugin/plugin.json +15 -5
  4. package/.cursor-plugin/plugin.json +15 -5
  5. package/.factory-plugin/plugin.json +2 -2
  6. package/.opencode/INSTALL.md +1 -1
  7. package/CHANGELOG.md +27 -6
  8. package/CLAUDE.md +2 -2
  9. package/GEMINI.md +2 -3
  10. package/README.md +51 -30
  11. package/_persona/summer/SKILL.md +5 -6
  12. package/commands/gameskill.md +103 -0
  13. package/dist/bin/summer.js +12 -4
  14. package/dist/commands/create.js +2 -0
  15. package/dist/commands/doctor.js +71 -1
  16. package/dist/commands/install.d.ts +17 -0
  17. package/dist/commands/install.js +63 -4
  18. package/dist/commands/memory.d.ts +14 -0
  19. package/dist/commands/memory.js +196 -0
  20. package/dist/commands/setup.js +8 -2
  21. package/dist/commands/skills.js +90 -6
  22. package/dist/commands/status.js +3 -0
  23. package/dist/lib/agent-config.d.ts +2 -2
  24. package/dist/lib/agent-config.js +134 -7
  25. package/dist/lib/api-client.d.ts +13 -0
  26. package/dist/lib/api-client.js +63 -26
  27. package/dist/lib/async-op-lifecycle.d.ts +60 -0
  28. package/dist/lib/async-op-lifecycle.js +83 -0
  29. package/dist/lib/project-memory.d.ts +33 -0
  30. package/dist/lib/project-memory.js +220 -0
  31. package/dist/lib/skills-registry.d.ts +134 -61
  32. package/dist/lib/skills-registry.js +102 -4
  33. package/dist/lib/version-check.d.ts +106 -0
  34. package/dist/lib/version-check.js +338 -0
  35. package/dist/mcp/server.d.ts +2 -0
  36. package/dist/mcp/server.js +81 -0
  37. package/dist/mcp/tools/debug-tools.js +32 -2
  38. package/dist/mcp/tools/project-tools.js +18 -6
  39. package/dist/mcp/tools/with-engine.d.ts +14 -0
  40. package/dist/mcp/tools/with-engine.js +52 -7
  41. package/gemini-extension.json +2 -2
  42. package/hooks/pre-commit-doctor.sh +0 -0
  43. package/hooks/run-hook.cmd +0 -0
  44. package/hooks/session-start.sh +0 -0
  45. package/package.json +6 -4
  46. package/skills/2d-assets/character-portrait/SKILL.md +1 -1
  47. package/skills/2d-assets/concept-art/SKILL.md +1 -1
  48. package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
  49. package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
  50. package/skills/2d-assets/pixel-art/SKILL.md +1 -1
  51. package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
  52. package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
  53. package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
  54. package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
  55. package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
  56. package/skills/3d-assets/character-model/SKILL.md +1 -1
  57. package/skills/3d-assets/organic-model/SKILL.md +1 -1
  58. package/skills/3d-assets/prop-model/SKILL.md +1 -1
  59. package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
  60. package/skills/README.md +37 -30
  61. package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
  62. package/skills/animation/animation-tree/SKILL.md +2 -2
  63. package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
  64. package/skills/animation/generate-motion/SKILL.md +1 -1
  65. package/skills/animation/procedural-animation/SKILL.md +2 -2
  66. package/skills/audio/adaptive-music/SKILL.md +1 -1
  67. package/skills/audio/audio-direction/SKILL.md +5 -5
  68. package/skills/audio/sound-effect/SKILL.md +2 -2
  69. package/skills/audio/voice-line/SKILL.md +25 -3
  70. package/skills/character-controllers/fps-controller/SKILL.md +5 -5
  71. package/skills/debugging/debug/SKILL.md +39 -1
  72. package/skills/deployment/export-and-ship/SKILL.md +4 -4
  73. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
  74. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
  75. package/skills/level-design/design-level/SKILL.md +5 -5
  76. package/skills/level-design/scene-to-level/SKILL.md +125 -0
  77. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
  78. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
  79. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
  80. package/skills/performance/tune-performance/SKILL.md +5 -5
  81. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  82. package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
  83. package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
  84. package/skills/scene-and-project/play/SKILL.md +1 -1
  85. package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
  86. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
  87. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  88. package/skills/video/animated-loop/SKILL.md +1 -1
  89. package/skills/video/cinematic-cutscene/SKILL.md +1 -1
  90. package/skills/video/trailer-shot/SKILL.md +1 -1
  91. package/skills/visual-effects/game-feel/SKILL.md +6 -6
  92. package/skills/workflow/brainstorming/SKILL.md +164 -0
  93. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
  94. package/skills/workflow/brainstorming/scripts/helper.js +88 -0
  95. package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
  96. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
  97. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
  98. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
  99. package/skills/workflow/brainstorming/visual-companion.md +287 -0
  100. package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
  101. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
  102. package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
  103. package/skills/workflow/gameskill/SKILL.md +111 -0
  104. package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
  105. package/skills/workflow/investigating-bugs/SKILL.md +296 -0
  106. package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
  107. package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
  108. package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
  109. package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
  110. package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
  111. package/skills/workflow/investigating-bugs/test-academic.md +14 -0
  112. package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
  113. package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
  114. package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
  115. package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
  116. package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
  117. package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
  118. package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
  119. package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
  120. package/skills/workflow/verification-before-completion/SKILL.md +139 -0
  121. package/skills/workflow/writing-plans/SKILL.md +152 -0
  122. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
  123. package/skills/workflow/writing-skills/SKILL.md +655 -0
  124. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  125. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  126. package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  127. package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  128. package/skills/workflow/writing-skills/render-graphs.js +168 -0
  129. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  130. package/dist/commands/assets.d.ts +0 -2
  131. package/dist/commands/assets.js +0 -189
  132. package/dist/commands/debug.d.ts +0 -2
  133. package/dist/commands/debug.js +0 -127
  134. package/dist/commands/doctor.test.d.ts +0 -1
  135. package/dist/commands/doctor.test.js +0 -29
  136. package/dist/commands/project.d.ts +0 -2
  137. package/dist/commands/project.js +0 -170
  138. package/dist/commands/scene.d.ts +0 -2
  139. package/dist/commands/scene.js +0 -290
  140. package/dist/lib/agent-config.test.d.ts +0 -1
  141. package/dist/lib/agent-config.test.js +0 -80
  142. package/dist/lib/format.test.d.ts +0 -1
  143. package/dist/lib/format.test.js +0 -36
  144. package/dist/lib/plugin-manifests.test.d.ts +0 -1
  145. package/dist/lib/plugin-manifests.test.js +0 -134
  146. package/dist/lib/with-engine.d.ts +0 -14
  147. package/dist/lib/with-engine.js +0 -109
  148. package/skills/_shared/collaborative-protocol.md +0 -50
  149. package/skills/_shared/gd-style.md +0 -121
  150. package/skills/_shared/godot-version.md +0 -32
  151. package/skills/_shared/mcp-tools-reference.md +0 -133
  152. package/skills/_shared/summer-folder.md +0 -44
  153. package/skills/_shared/template-registry.md +0 -40
  154. package/skills/_tests/runner.md +0 -90
  155. package/skills/_tests/specs/3d-lighting.md +0 -19
  156. package/skills/_tests/specs/art-direction.md +0 -115
  157. package/skills/_tests/specs/asset-strategy.md +0 -19
  158. package/skills/_tests/specs/audio-direction.md +0 -123
  159. package/skills/_tests/specs/brainstorm-game.md +0 -99
  160. package/skills/_tests/specs/debug.md +0 -94
  161. package/skills/_tests/specs/design-level.md +0 -113
  162. package/skills/_tests/specs/design-mechanic.md +0 -112
  163. package/skills/_tests/specs/design-npc.md +0 -143
  164. package/skills/_tests/specs/export-and-ship.md +0 -114
  165. package/skills/_tests/specs/fps-controller.md +0 -113
  166. package/skills/_tests/specs/gdscript-patterns.md +0 -19
  167. package/skills/_tests/specs/host-authoritative-state.md +0 -104
  168. package/skills/_tests/specs/make-game.md +0 -19
  169. package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
  170. package/skills/_tests/specs/play.md +0 -88
  171. package/skills/_tests/specs/scene-composition.md +0 -19
  172. package/skills/_tests/specs/setup-multiplayer.md +0 -106
  173. package/skills/_tests/specs/tune-performance.md +0 -104
  174. package/skills/_tests/specs/ui-basics.md +0 -19
  175. package/skills/_tests/specs/vfx.md +0 -116
  176. package/skills/catalog.yaml +0 -991
@@ -10,6 +10,7 @@ import { openCommand } from "../commands/open.js";
10
10
  import { installCommand } from "../commands/install.js";
11
11
  import { createCommand } from "../commands/create.js";
12
12
  import { listCommand } from "../commands/list.js";
13
+ import { memoryCommand } from "../commands/memory.js";
13
14
  import { skillsCommand } from "../commands/skills.js";
14
15
  import { orchestratorCommand } from "../commands/orchestrator.js";
15
16
  import { setupCommand } from "../commands/setup.js";
@@ -34,6 +35,7 @@ program.addCommand(runCommand);
34
35
  program.addCommand(openCommand);
35
36
  program.addCommand(createCommand);
36
37
  program.addCommand(listCommand);
38
+ program.addCommand(memoryCommand);
37
39
  program.addCommand(skillsCommand);
38
40
  program.addCommand(mcpCommand);
39
41
  program.addCommand(setupCommand);
@@ -49,15 +51,21 @@ function printIntro(version) {
49
51
  console.log("");
50
52
  console.log(` ${sym.arrow()} ${c.bold("Summer Engine")} ${c.dim("v" + version)} ${c.dim("·")} AI-native game engine`);
51
53
  console.log("");
52
- console.log(` ${c.bold("Get started")}`);
53
- console.log(` ${c.dim("$")} summer setup claude-code ${c.dim("Configure Claude Code (or codex/cursor/windsurf)")}`);
54
- console.log(` ${c.dim("$")} summer create 3d-basic my-game ${c.dim("Start a project")}`);
55
- console.log(` ${c.dim("$")} summer doctor ${c.dim("Check setup")}`);
54
+ console.log(` ${c.bold("Setup wizard")}`);
55
+ console.log(` Open Claude Code, Cursor, Codex, Copilot, Windsurf, or another supported agent and paste:`);
56
+ console.log(` ${c.brand("\"Install Summer Engine and let's make a game.\"")}`);
57
+ console.log("");
58
+ console.log(` ${c.bold("Manual commands")}`);
59
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest setup claude-code --yes`);
60
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest create 3d-basic my-game`);
61
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest memory`);
62
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest doctor`);
56
63
  console.log("");
57
64
  console.log(` ${c.bold("Slash commands")} ${c.dim("(in your AI agent)")}`);
58
65
  console.log(` ${c.brand("/debug")} ${c.dim("Triage and fix a bug end-to-end")}`);
59
66
  console.log(` ${c.brand("/play")} ${c.dim("Run the game and report state")}`);
60
67
  console.log("");
61
68
  console.log(` ${c.dim("Docs:")} https://summerengine.com/docs`);
69
+ console.log(` ${c.dim("Source:")} https://github.com/SummerEngine/summer`);
62
70
  console.log("");
63
71
  }
@@ -42,6 +42,7 @@ export const createCommand = new Command("create")
42
42
  console.log(`\nProject created at ${fullPath}`);
43
43
  console.log("\nNext steps:");
44
44
  console.log(` summer run ${dirName}`);
45
+ console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
45
46
  return;
46
47
  }
47
48
  // Fall back to remote template lookup
@@ -86,6 +87,7 @@ export const createCommand = new Command("create")
86
87
  console.log("Source: " + remote.url);
87
88
  console.log("\nNext steps:");
88
89
  console.log(` summer run ${dirName}`);
90
+ console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
89
91
  });
90
92
  function projectGodot(name, mainScene) {
91
93
  return `; Summer Engine Project
@@ -6,6 +6,8 @@ import { Command } from "commander";
6
6
  import { getAuthToken, getUserInfo } from "../lib/auth.js";
7
7
  import { checkEngineHealth, getApiPort, getApiToken } from "../lib/engine.js";
8
8
  import { brandLine, c, pad, sym, tildeify } from "../lib/format.js";
9
+ import { getProjectMemorySummary } from "../lib/project-memory.js";
10
+ import { buildCliVersionCheck, buildSkillsVersionCheck, defaultSkillMarkerCandidates, fetchLatestRegistryVersion, } from "../lib/version-check.js";
9
11
  const require = createRequire(import.meta.url);
10
12
  const { version } = require("../../package.json");
11
13
  const MAC_ENGINE_PATHS = [
@@ -18,7 +20,7 @@ const WIN_ENGINE_PATHS = [
18
20
  `${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
19
21
  ];
20
22
  export const doctorCommand = new Command("doctor")
21
- .description("Diagnose Node, login, engine, local API, and MCP boot")
23
+ .description("Diagnose Node, login, engine, project memory, local API, and MCP boot")
22
24
  .option("--json", "Print diagnostics as JSON")
23
25
  .action(async (opts) => {
24
26
  if (!opts.json) {
@@ -42,9 +44,12 @@ export async function runDoctor(options = {}) {
42
44
  status: "ok",
43
45
  message: `v${version}`,
44
46
  });
47
+ checks.push(await checkCliVersionCurrent());
48
+ checks.push(checkSkillsVersion());
45
49
  checks.push(await checkLogin());
46
50
  checks.push(checkEngineInstall());
47
51
  checks.push(await checkLocalApi());
52
+ checks.push(await checkProjectMemory());
48
53
  checks.push(await checkMcpBoot());
49
54
  const result = summarizeChecks(checks);
50
55
  if (!options.quiet) {
@@ -74,6 +79,33 @@ function checkNodeVersion() {
74
79
  message: `${process.version} (need 18+)`,
75
80
  };
76
81
  }
82
+ async function checkCliVersionCurrent() {
83
+ const registry = await fetchLatestRegistryVersion();
84
+ const result = buildCliVersionCheck({
85
+ installedVersion: version,
86
+ registry,
87
+ });
88
+ return {
89
+ id: "cli-version-current",
90
+ label: "CLI up to date",
91
+ status: result.status,
92
+ message: result.message,
93
+ details: result.details,
94
+ };
95
+ }
96
+ function checkSkillsVersion() {
97
+ const result = buildSkillsVersionCheck({
98
+ installedCliVersion: version,
99
+ candidates: defaultSkillMarkerCandidates(),
100
+ });
101
+ return {
102
+ id: "skills-version-stale",
103
+ label: "Skills up to date",
104
+ status: result.status,
105
+ message: result.message,
106
+ details: result.details,
107
+ };
108
+ }
77
109
  async function checkLogin() {
78
110
  const token = await getAuthToken();
79
111
  const user = await getUserInfo();
@@ -147,6 +179,44 @@ async function checkLocalApi() {
147
179
  },
148
180
  };
149
181
  }
182
+ async function checkProjectMemory() {
183
+ const port = await getApiPort();
184
+ const health = await checkEngineHealth(port);
185
+ if (!health?.project_path) {
186
+ return {
187
+ id: "project-memory",
188
+ label: "Project Memory",
189
+ status: "ok",
190
+ message: "no project open",
191
+ };
192
+ }
193
+ const summary = getProjectMemorySummary(health.project_path);
194
+ if (!summary.present) {
195
+ return {
196
+ id: "project-memory",
197
+ label: "Project Memory",
198
+ status: "warning",
199
+ message: ".summer not created yet",
200
+ details: {
201
+ projectPath: health.project_path,
202
+ reason: summary.reason,
203
+ recommendedAction: "Ask your agent to run summer:brainstorm-game.",
204
+ },
205
+ };
206
+ }
207
+ return {
208
+ id: "project-memory",
209
+ label: "Project Memory",
210
+ status: "ok",
211
+ message: `${summary.files.length} files, ${summary.structured.fileCount} memory, ${summary.structured.lockedCount} locked`,
212
+ details: {
213
+ projectPath: health.project_path,
214
+ files: summary.files.length,
215
+ memoryFiles: summary.structured.fileCount,
216
+ locked: summary.structured.lockedCount,
217
+ },
218
+ };
219
+ }
150
220
  async function checkMcpBoot() {
151
221
  const cliPath = process.argv[1];
152
222
  if (!cliPath) {
@@ -1,2 +1,19 @@
1
1
  import { Command } from "commander";
2
+ /** Guard a download URL from the releases endpoint. A missing/empty URL (an
3
+ * outdated or misconfigured endpoint) otherwise becomes a cryptic
4
+ * fetch(undefined) failure — surface it clearly with a manual fallback. */
5
+ export declare function requireDownloadUrl(value: string | undefined, label: string): string;
6
+ /** Verify a downloaded file's sha256 against the expected hash from the releases
7
+ * endpoint. Skipped when no expected hash is published. Catches a truncated or
8
+ * corrupted download (a ~1GB install over a flaky network) before it becomes a
9
+ * confusing DMG-mount / installer failure downstream. */
10
+ export declare function verifyChecksum(actual: string, expected: string | undefined, label: string): void;
11
+ /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
12
+ * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
13
+ * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
14
+ * installer with "unexpected argument '/S' found" — this is the wizard bug. */
15
+ export declare function windowsInstallerArgs(customPath?: string): string;
2
16
  export declare const installCommand: Command;
17
+ export declare function downloadFile(url: string, dest: string): Promise<{
18
+ sha256: string;
19
+ }>;
@@ -1,11 +1,41 @@
1
1
  import { Command } from "commander";
2
2
  import { execSync } from "child_process";
3
+ import { createHash } from "crypto";
3
4
  import { createWriteStream, existsSync, mkdirSync } from "fs";
4
5
  import { pipeline } from "stream/promises";
5
6
  import { Readable } from "stream";
6
7
  import { tmpdir, platform } from "os";
7
8
  import { join } from "path";
8
9
  const RELEASES_URL = "https://www.summerengine.com/api/desktop/releases";
10
+ /** Guard a download URL from the releases endpoint. A missing/empty URL (an
11
+ * outdated or misconfigured endpoint) otherwise becomes a cryptic
12
+ * fetch(undefined) failure — surface it clearly with a manual fallback. */
13
+ export function requireDownloadUrl(value, label) {
14
+ if (!value || value.trim().length === 0) {
15
+ throw new Error(`Release info is missing the ${label} download URL — the releases endpoint may be ` +
16
+ `outdated. Download manually from https://summerengine.com/download`);
17
+ }
18
+ return value;
19
+ }
20
+ /** Verify a downloaded file's sha256 against the expected hash from the releases
21
+ * endpoint. Skipped when no expected hash is published. Catches a truncated or
22
+ * corrupted download (a ~1GB install over a flaky network) before it becomes a
23
+ * confusing DMG-mount / installer failure downstream. */
24
+ export function verifyChecksum(actual, expected, label) {
25
+ if (!expected || expected.trim().length === 0)
26
+ return; // nothing published to verify against
27
+ if (actual.toLowerCase() !== expected.toLowerCase()) {
28
+ throw new Error(`${label} checksum mismatch — the download was corrupted or incomplete ` +
29
+ `(expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…). Re-run \`summer install\`.`);
30
+ }
31
+ }
32
+ /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
33
+ * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
34
+ * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
35
+ * installer with "unexpected argument '/S' found" — this is the wizard bug. */
36
+ export function windowsInstallerArgs(customPath) {
37
+ return customPath ? `--silent --installto "${customPath}"` : "--silent";
38
+ }
9
39
  export const installCommand = new Command("install")
10
40
  .description("Download and install Summer Engine")
11
41
  .option("--path <dir>", "Custom install directory")
@@ -42,6 +72,7 @@ async function installMac(releases, customPath) {
42
72
  console.error("No macOS release found.");
43
73
  process.exit(1);
44
74
  }
75
+ const dmgUrl = requireDownloadUrl(info.dmg_url, "macOS DMG");
45
76
  console.log(`Latest version: ${info.version}`);
46
77
  const destApp = customPath || "/Applications/Summer.app";
47
78
  if (existsSync(destApp)) {
@@ -50,7 +81,18 @@ async function installMac(releases, customPath) {
50
81
  }
51
82
  const dmgPath = join(tmpdir(), `Summer-v${info.version}.dmg`);
52
83
  console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
53
- await downloadFile(info.dmg_url, dmgPath);
84
+ try {
85
+ const { sha256 } = await downloadFile(dmgUrl, dmgPath);
86
+ verifyChecksum(sha256, info.dmg_sha256, "DMG");
87
+ }
88
+ catch (err) {
89
+ try {
90
+ execSync(`rm -f "${dmgPath}"`, { stdio: "ignore" });
91
+ }
92
+ catch { /* best effort */ }
93
+ console.error(err instanceof Error ? err.message : String(err));
94
+ process.exit(1);
95
+ }
54
96
  console.log("Mounting DMG...");
55
97
  const mountOutput = execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -noautoopen`, {
56
98
  encoding: "utf-8",
@@ -88,13 +130,25 @@ async function installWindows(releases, customPath) {
88
130
  console.error("No Windows release found.");
89
131
  process.exit(1);
90
132
  }
133
+ const exeUrl = requireDownloadUrl(info.url, "Windows installer");
91
134
  console.log(`Latest version: ${info.version}`);
92
135
  const exePath = join(tmpdir(), `Summer-v${info.version}.exe`);
93
136
  console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
94
- await downloadFile(info.url, exePath);
137
+ try {
138
+ const { sha256 } = await downloadFile(exeUrl, exePath);
139
+ verifyChecksum(sha256, info.sha256, "installer");
140
+ }
141
+ catch (err) {
142
+ try {
143
+ execSync(`del "${exePath}"`, { stdio: "ignore" });
144
+ }
145
+ catch { /* best effort */ }
146
+ console.error(err instanceof Error ? err.message : String(err));
147
+ process.exit(1);
148
+ }
95
149
  console.log("Running installer...");
96
150
  try {
97
- execSync(`"${exePath}" /S${customPath ? ` /D=${customPath}` : ""}`, {
151
+ execSync(`"${exePath}" ${windowsInstallerArgs(customPath)}`, {
98
152
  stdio: "inherit",
99
153
  });
100
154
  }
@@ -111,7 +165,7 @@ async function installWindows(releases, customPath) {
111
165
  console.log(" summer login # Sign in to your account");
112
166
  console.log(" summer run # Launch the engine");
113
167
  }
114
- async function downloadFile(url, dest) {
168
+ export async function downloadFile(url, dest) {
115
169
  const res = await fetch(url);
116
170
  if (!res.ok || !res.body) {
117
171
  throw new Error(`Download failed: HTTP ${res.status}`);
@@ -121,9 +175,13 @@ async function downloadFile(url, dest) {
121
175
  mkdirSync(dir, { recursive: true });
122
176
  const fileStream = createWriteStream(dest);
123
177
  const readable = Readable.fromWeb(res.body);
178
+ // Hash while streaming (the 'data' listener and the pipe both see every chunk
179
+ // in flowing mode), so we get integrity for free without a second read pass.
180
+ const hash = createHash("sha256");
124
181
  let downloaded = 0;
125
182
  const total = parseInt(res.headers.get("content-length") || "0", 10);
126
183
  readable.on("data", (chunk) => {
184
+ hash.update(chunk);
127
185
  downloaded += chunk.length;
128
186
  if (total > 0) {
129
187
  const pct = Math.round((downloaded / total) * 100);
@@ -132,4 +190,5 @@ async function downloadFile(url, dest) {
132
190
  });
133
191
  await pipeline(readable, fileStream);
134
192
  process.stdout.write("\n");
193
+ return { sha256: hash.digest("hex") };
135
194
  }
@@ -0,0 +1,14 @@
1
+ import { Command } from "commander";
2
+ import { type ProjectMemorySummary } from "../lib/project-memory.js";
3
+ interface ResolvedMemoryProject {
4
+ projectPath: string | null;
5
+ source: "option" | "engine" | "cwd" | "none";
6
+ reason?: string;
7
+ }
8
+ export declare const memoryCommand: Command;
9
+ export declare function resolveMemoryProject(projectOption?: string): Promise<ResolvedMemoryProject>;
10
+ export declare function findProjectRoot(startDir: string): string | null;
11
+ export declare function resolveMemoryFilePath(projectPath: string, requested: string): string;
12
+ export declare function formatMemorySummary(projectPath: string, summary: ProjectMemorySummary): string;
13
+ export declare function formatStatusMemoryLine(summary: ProjectMemorySummary): string;
14
+ export {};
@@ -0,0 +1,196 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { Command } from "commander";
4
+ import { checkEngineHealth, getApiPort } from "../lib/engine.js";
5
+ import { getProjectMemorySummary, } from "../lib/project-memory.js";
6
+ import { c, pad, tildeify } from "../lib/format.js";
7
+ export const memoryCommand = new Command("memory")
8
+ .description("Inspect project memory in .summer")
9
+ .option("--project <path>", "Project directory to inspect")
10
+ .option("--json", "Print memory summary as JSON")
11
+ .action(async (opts) => {
12
+ const resolvedProject = await resolveMemoryProject(opts.project);
13
+ if (!resolvedProject.projectPath) {
14
+ if (opts.json) {
15
+ console.log(JSON.stringify({ ok: false, ...resolvedProject }, null, 2));
16
+ }
17
+ else {
18
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
19
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
20
+ }
21
+ process.exit(1);
22
+ }
23
+ const summary = getProjectMemorySummary(resolvedProject.projectPath);
24
+ if (opts.json) {
25
+ console.log(JSON.stringify({
26
+ ok: true,
27
+ projectPath: resolvedProject.projectPath,
28
+ source: resolvedProject.source,
29
+ memory: summary,
30
+ }, null, 2));
31
+ return;
32
+ }
33
+ console.log(formatMemorySummary(resolvedProject.projectPath, summary));
34
+ });
35
+ memoryCommand
36
+ .command("show <file>")
37
+ .description("Print a .summer memory Markdown file")
38
+ .option("--project <path>", "Project directory to inspect")
39
+ .action(async (file, opts) => {
40
+ const resolvedProject = await resolveMemoryProject(opts.project);
41
+ if (!resolvedProject.projectPath) {
42
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
43
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
44
+ process.exit(1);
45
+ }
46
+ const filePath = resolveMemoryFilePath(resolvedProject.projectPath, file);
47
+ console.log(readFileSync(filePath, "utf-8"));
48
+ });
49
+ memoryCommand
50
+ .command("path")
51
+ .description("Print the .summer directory path for the current project")
52
+ .option("--project <path>", "Project directory to inspect")
53
+ .action(async (opts) => {
54
+ const resolvedProject = await resolveMemoryProject(opts.project);
55
+ if (!resolvedProject.projectPath) {
56
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
57
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
58
+ process.exit(1);
59
+ }
60
+ console.log(join(resolvedProject.projectPath, ".summer"));
61
+ });
62
+ export async function resolveMemoryProject(projectOption) {
63
+ if (projectOption) {
64
+ const projectPath = resolve(projectOption);
65
+ if (!existsSync(projectPath)) {
66
+ return {
67
+ projectPath: null,
68
+ source: "option",
69
+ reason: `Project directory not found: ${projectPath}`,
70
+ };
71
+ }
72
+ if (!existsSync(join(projectPath, "project.godot"))) {
73
+ return {
74
+ projectPath: null,
75
+ source: "option",
76
+ reason: `No project.godot found in ${projectPath}.`,
77
+ };
78
+ }
79
+ return { projectPath, source: "option" };
80
+ }
81
+ const cwdProject = findProjectRoot(process.cwd());
82
+ if (cwdProject)
83
+ return { projectPath: cwdProject, source: "cwd" };
84
+ const port = await getApiPort();
85
+ const health = await checkEngineHealth(port);
86
+ if (health?.project_path && existsSync(join(health.project_path, "project.godot"))) {
87
+ return { projectPath: health.project_path, source: "engine" };
88
+ }
89
+ return {
90
+ projectPath: null,
91
+ source: "none",
92
+ reason: "No Summer project found from engine state or current directory.",
93
+ };
94
+ }
95
+ export function findProjectRoot(startDir) {
96
+ let current = resolve(startDir);
97
+ while (true) {
98
+ if (existsSync(join(current, "project.godot")))
99
+ return current;
100
+ const parent = dirname(current);
101
+ if (parent === current)
102
+ return null;
103
+ current = parent;
104
+ }
105
+ }
106
+ export function resolveMemoryFilePath(projectPath, requested) {
107
+ const summerDir = resolve(projectPath, ".summer");
108
+ const rawPath = requested.startsWith(".summer/")
109
+ ? resolve(projectPath, requested)
110
+ : resolve(summerDir, requested);
111
+ const withinSummer = isPathInside(summerDir, rawPath);
112
+ if (!withinSummer) {
113
+ throw new Error("Memory files must be inside the project's .summer directory.");
114
+ }
115
+ if (!existsSync(rawPath)) {
116
+ throw new Error(`Memory file not found: ${requested}`);
117
+ }
118
+ if (!statSync(rawPath).isFile()) {
119
+ throw new Error(`Memory path is not a file: ${requested}`);
120
+ }
121
+ return rawPath;
122
+ }
123
+ export function formatMemorySummary(projectPath, summary) {
124
+ const lines = [];
125
+ lines.push(c.bold("Project Memory"));
126
+ lines.push("");
127
+ lines.push(` Project: ${tildeify(projectPath)}`);
128
+ lines.push(` Folder: ${tildeify(join(projectPath, ".summer"))}`);
129
+ lines.push("");
130
+ if (!summary.present) {
131
+ lines.push(` ${summary.reason ?? "No .summer memory found yet."}`);
132
+ lines.push("");
133
+ lines.push(" Agents create this through Summer skills.");
134
+ lines.push(" Start with .summer/GameSoul.md for the project brief.");
135
+ lines.push(" Use .summer/memory/ for locked facts like voice IDs and world canon.");
136
+ return lines.join("\n");
137
+ }
138
+ const totalFiles = summary.files.length;
139
+ const structured = summary.structured;
140
+ lines.push(` Status: ${totalFiles} .summer Markdown file${totalFiles === 1 ? "" : "s"}, ` +
141
+ `${structured.fileCount} structured memory file${structured.fileCount === 1 ? "" : "s"}, ` +
142
+ `${structured.lockedCount} locked`);
143
+ lines.push("");
144
+ const canonicalRows = canonicalRowsFromSummary(summary);
145
+ if (canonicalRows.length > 0) {
146
+ lines.push(c.bold("Canonical"));
147
+ for (const [label, file] of canonicalRows) {
148
+ lines.push(formatMemoryFileLine(label, file));
149
+ }
150
+ lines.push("");
151
+ }
152
+ if (structured.files.length > 0) {
153
+ lines.push(c.bold("Structured"));
154
+ for (const file of structured.files) {
155
+ lines.push(formatMemoryFileLine(file.kind, file));
156
+ }
157
+ if (structured.truncated) {
158
+ lines.push(" ...more files omitted from summary");
159
+ }
160
+ lines.push("");
161
+ }
162
+ lines.push("Read a file:");
163
+ lines.push(" summer memory show .summer/memory/casting/voices.md");
164
+ lines.push("");
165
+ lines.push("Rule:");
166
+ lines.push(" Markdown is canonical. Changing priority: locked memory requires explicit user confirmation.");
167
+ return lines.join("\n");
168
+ }
169
+ export function formatStatusMemoryLine(summary) {
170
+ if (!summary.present)
171
+ return "Memory: No .summer folder yet";
172
+ const fileCount = summary.files.length;
173
+ const structuredCount = summary.structured.fileCount;
174
+ const lockedCount = summary.structured.lockedCount;
175
+ return `Memory: ${fileCount} .summer file${fileCount === 1 ? "" : "s"}, ${structuredCount} memory file${structuredCount === 1 ? "" : "s"}, ${lockedCount} locked`;
176
+ }
177
+ function canonicalRowsFromSummary(summary) {
178
+ const rows = [
179
+ ["brief", summary.canonical.gameSoul],
180
+ ["art", summary.canonical.artBible],
181
+ ["audio", summary.canonical.audioBible],
182
+ ["build", summary.canonical.buildPlan],
183
+ ["legacy-casting", summary.canonical.legacyVoiceCast],
184
+ ];
185
+ return rows.filter((row) => row[1] !== null);
186
+ }
187
+ function formatMemoryFileLine(label, file) {
188
+ const lock = file.locked ? "locked" : "";
189
+ const title = file.title ? ` - ${file.title}` : "";
190
+ return ` ${pad(label, 15)} ${pad(file.path, 42)} ${lock}${title}`;
191
+ }
192
+ function isPathInside(parent, child) {
193
+ const relativePath = relative(parent, child);
194
+ return (relativePath === "" ||
195
+ (!relativePath.startsWith("..") && !isAbsolute(relativePath)));
196
+ }
@@ -14,6 +14,8 @@ const AGENT_LABEL = {
14
14
  cline: "Cline",
15
15
  "roo-code": "Roo Code",
16
16
  gemini: "Gemini CLI",
17
+ "github-copilot": "GitHub Copilot CLI",
18
+ "vscode-copilot": "GitHub Copilot in VS Code",
17
19
  opencode: "OpenCode",
18
20
  };
19
21
  export const setupCommand = new Command("setup")
@@ -25,6 +27,7 @@ export const setupCommand = new Command("setup")
25
27
  .option("--print", "Print the MCP config snippet instead of writing files")
26
28
  .option("--yes", "Apply practical setup steps without prompting")
27
29
  .option("--json", "Print setup result as JSON")
30
+ .option("--force", "Overwrite existing skill content (passes --force through to skills install)")
28
31
  .action(async (agentArg, opts) => {
29
32
  const agent = resolveAgent(agentArg, opts.agent);
30
33
  const scope = resolveScope(opts.scope);
@@ -37,6 +40,7 @@ export const setupCommand = new Command("setup")
37
40
  const skills = setupRecommendedSkills(agent, {
38
41
  dryRun: Boolean(opts.dryRun || opts.print),
39
42
  yes: Boolean(opts.yes),
43
+ force: Boolean(opts.force),
40
44
  });
41
45
  const doctor = await runDoctor({ quiet: true });
42
46
  if (opts.json) {
@@ -72,7 +76,7 @@ function resolveScope(scopeOpt) {
72
76
  return parsed;
73
77
  }
74
78
  function setupRecommendedSkills(agent, options) {
75
- const invocation = skillInstallInvocation(agent);
79
+ const invocation = skillInstallInvocation(agent, { force: options.force });
76
80
  if (!invocation) {
77
81
  return {
78
82
  status: "skipped",
@@ -107,13 +111,15 @@ function setupRecommendedSkills(agent, options) {
107
111
  stderr: result.stderr.trim(),
108
112
  };
109
113
  }
110
- function skillInstallInvocation(agent) {
114
+ function skillInstallInvocation(agent, opts = { force: false }) {
111
115
  const cliPath = process.argv[1];
112
116
  if (!cliPath)
113
117
  return null;
114
118
  const command = cliPath.endsWith(".js") ? process.execPath : cliPath;
115
119
  const prefix = cliPath.endsWith(".js") ? [cliPath] : [];
116
120
  const baseArgs = ["skills", "install", "--recommended", "--agent", agent];
121
+ if (opts.force)
122
+ baseArgs.push("--force");
117
123
  return {
118
124
  command,
119
125
  args: [...prefix, ...baseArgs],