summer-engine 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +15 -11
  4. package/.cursor-plugin/plugin.json +5 -3
  5. package/.factory-plugin/plugin.json +3 -2
  6. package/AGENTS.md +1 -1
  7. package/CHANGELOG.md +37 -0
  8. package/README.md +20 -9
  9. package/dist/cli/commands/config.js +3 -2
  10. package/dist/cli/commands/create.js +12 -3
  11. package/dist/cli/commands/list.d.ts +5 -0
  12. package/dist/cli/commands/list.js +30 -13
  13. package/dist/cli/commands/memory.js +6 -2
  14. package/dist/cli/commands/ops.d.ts +20 -3
  15. package/dist/cli/commands/ops.js +55 -27
  16. package/dist/cli/commands/setup.d.ts +2 -0
  17. package/dist/cli/commands/setup.js +219 -16
  18. package/dist/cli/commands/skills.js +20 -7
  19. package/dist/cli/commands/status.d.ts +26 -0
  20. package/dist/cli/commands/status.js +36 -21
  21. package/dist/core/capabilities/apps.d.ts +10 -10
  22. package/dist/core/capabilities/audit/args.d.ts +6 -6
  23. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  24. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  25. package/dist/core/capabilities/hdri-import.d.ts +2 -2
  26. package/dist/core/capabilities/motion-generation.d.ts +2 -2
  27. package/dist/core/capabilities/placement.d.ts +23 -23
  28. package/dist/core/capabilities/runtime-control.d.ts +38 -38
  29. package/dist/core/capabilities/seeing/args.d.ts +16 -16
  30. package/dist/core/capabilities/ui-control.d.ts +2 -2
  31. package/dist/core/capabilities/world-snapshot.d.ts +4 -4
  32. package/dist/core/library-search.d.ts +2 -2
  33. package/dist/core/ops/areas.d.ts +11 -0
  34. package/dist/core/ops/areas.js +24 -0
  35. package/dist/core/ops/catalog.d.ts +4 -0
  36. package/dist/core/ops/catalog.js +13 -1
  37. package/dist/core/ops/cloud.d.ts +10 -2
  38. package/dist/core/ops/cloud.js +51 -28
  39. package/dist/core/ops/contract.d.ts +2 -1
  40. package/dist/core/ops/contract.js +2 -10
  41. package/dist/core/ops/public-catalog.d.ts +48 -0
  42. package/dist/core/ops/public-catalog.js +56 -0
  43. package/dist/core/ops/search.d.ts +0 -1
  44. package/dist/core/ops/search.js +169 -37
  45. package/dist/installer/agent-config.js +5 -0
  46. package/dist/installer/agent-table.d.ts +9 -0
  47. package/dist/installer/agent-table.js +23 -2
  48. package/dist/installer/cli-permissions.d.ts +65 -0
  49. package/dist/installer/cli-permissions.js +182 -0
  50. package/dist/installer/setup.d.ts +2 -0
  51. package/dist/installer/setup.js +3 -0
  52. package/dist/installer/skills-refresh.d.ts +29 -0
  53. package/dist/installer/skills-refresh.js +261 -0
  54. package/dist/installer/version-check.d.ts +22 -2
  55. package/dist/installer/version-check.js +66 -9
  56. package/dist/mcp/ops-surface.js +16 -6
  57. package/dist/mcp/server.d.ts +10 -0
  58. package/dist/mcp/server.js +32 -3
  59. package/dist/project-memory/agent-instructions.d.ts +47 -0
  60. package/dist/project-memory/agent-instructions.js +226 -0
  61. package/gemini-extension.json +3 -2
  62. package/integrations/README.md +1 -1
  63. package/integrations/claude/README.md +6 -0
  64. package/integrations/claude/manifest-target.json +4 -0
  65. package/integrations/gemini/README.md +11 -0
  66. package/integrations/windsurf/README.md +1 -1
  67. package/library/skills/3d-lighting/SKILL.md +16 -0
  68. package/library/skills/README.md +5 -4
  69. package/library/skills/adaptive-music/SKILL.md +21 -0
  70. package/library/skills/agent-playtesting/SKILL.md +22 -0
  71. package/library/skills/ai-characters-in-games/SKILL.md +15 -0
  72. package/library/skills/ambient-bed/SKILL.md +16 -0
  73. package/library/skills/animated-loop/SKILL.md +19 -0
  74. package/library/skills/animation-tree/SKILL.md +26 -0
  75. package/library/skills/art-direction/SKILL.md +14 -0
  76. package/library/skills/asset-strategy/SKILL.md +30 -0
  77. package/library/skills/audio-direction/SKILL.md +16 -0
  78. package/library/skills/auto-fire-targeting/SKILL.md +14 -0
  79. package/library/skills/brainstorm-game/SKILL.md +13 -0
  80. package/library/skills/browse-templates/SKILL.md +10 -0
  81. package/library/skills/camera-rigs/SKILL.md +20 -0
  82. package/library/skills/character-animation-wiring/SKILL.md +24 -0
  83. package/library/skills/character-model/SKILL.md +28 -0
  84. package/library/skills/character-movement/SKILL.md +24 -0
  85. package/library/skills/character-portrait/SKILL.md +15 -0
  86. package/library/skills/cinematic-cutscene/SKILL.md +21 -0
  87. package/library/skills/combat-basics/SKILL.md +19 -0
  88. package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
  89. package/library/skills/concept-art/SKILL.md +13 -0
  90. package/library/skills/concept-to-level/SKILL.md +20 -1
  91. package/library/skills/concept-to-level/resource.yaml +2 -2
  92. package/library/skills/create-asset-sheet/SKILL.md +18 -0
  93. package/library/skills/debug/SKILL.md +32 -0
  94. package/library/skills/debugging-game-feel/SKILL.md +12 -0
  95. package/library/skills/design-level/SKILL.md +17 -0
  96. package/library/skills/design-mechanic/SKILL.md +17 -0
  97. package/library/skills/design-npc/SKILL.md +21 -0
  98. package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
  99. package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
  100. package/library/skills/environment-kit/SKILL.md +23 -0
  101. package/library/skills/export-and-ship/SKILL.md +23 -0
  102. package/library/skills/fabricating-assets/SKILL.md +23 -0
  103. package/library/skills/facial-and-lipsync/SKILL.md +22 -0
  104. package/library/skills/fps-controller/SKILL.md +17 -0
  105. package/library/skills/game-feel/SKILL.md +26 -0
  106. package/library/skills/gameskill/SKILL.md +11 -0
  107. package/library/skills/gdscript-patterns/SKILL.md +15 -0
  108. package/library/skills/generate-motion/SKILL.md +21 -0
  109. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
  110. package/library/skills/grow-analytics/SKILL.md +10 -0
  111. package/library/skills/headless-scripting/SKILL.md +13 -0
  112. package/library/skills/in-game-purchases/SKILL.md +11 -0
  113. package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
  114. package/library/skills/make-game/SKILL.md +29 -0
  115. package/library/skills/multiplayer/SKILL.md +12 -0
  116. package/library/skills/multiplayer-movement/SKILL.md +18 -0
  117. package/library/skills/multiplayer-project/SKILL.md +17 -0
  118. package/library/skills/multiplayer-publish/SKILL.md +17 -0
  119. package/library/skills/multiplayer-state/SKILL.md +16 -0
  120. package/library/skills/multiplayer-testing/SKILL.md +15 -0
  121. package/library/skills/music-track/SKILL.md +16 -0
  122. package/library/skills/navigate-summer/SKILL.md +15 -0
  123. package/library/skills/new-project/SKILL.md +11 -0
  124. package/library/skills/organic-model/SKILL.md +24 -0
  125. package/library/skills/pixel-art/SKILL.md +15 -0
  126. package/library/skills/play/SKILL.md +18 -0
  127. package/library/skills/playtesting-a-feature/SKILL.md +23 -0
  128. package/library/skills/procedural-animation/SKILL.md +18 -0
  129. package/library/skills/prop-model/SKILL.md +23 -0
  130. package/library/skills/publish-your-game/SKILL.md +27 -0
  131. package/library/skills/release-readiness/SKILL.md +12 -0
  132. package/library/skills/remote-deploy/SKILL.md +14 -0
  133. package/library/skills/retarget/SKILL.md +20 -0
  134. package/library/skills/running-in-the-cloud/SKILL.md +10 -0
  135. package/library/skills/save-load/SKILL.md +15 -0
  136. package/library/skills/scene-composition/SKILL.md +17 -0
  137. package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
  138. package/library/skills/scene-scripting/SKILL.md +30 -0
  139. package/library/skills/scene-to-level/SKILL.md +22 -0
  140. package/library/skills/skill-test/SKILL.md +10 -0
  141. package/library/skills/skybox-panorama/SKILL.md +20 -0
  142. package/library/skills/sound-effect/SKILL.md +23 -0
  143. package/library/skills/spatial-placement/SKILL.md +30 -0
  144. package/library/skills/sprite-sheet/SKILL.md +23 -0
  145. package/library/skills/store-art/SKILL.md +11 -0
  146. package/library/skills/store-listing/SKILL.md +10 -0
  147. package/library/skills/summer-analytics/SKILL.md +15 -0
  148. package/library/skills/summer-apps/SKILL.md +29 -0
  149. package/library/skills/summer-assets/SKILL.md +31 -1
  150. package/library/skills/summer-assets/resource.yaml +2 -2
  151. package/library/skills/summer-cli/SKILL.md +133 -0
  152. package/library/skills/summer-cli/resource.yaml +31 -0
  153. package/library/skills/summer-friends/SKILL.md +14 -0
  154. package/library/skills/summer-leaderboards/SKILL.md +16 -0
  155. package/library/skills/summer-match-results/SKILL.md +17 -0
  156. package/library/skills/summer-matchmaking/SKILL.md +17 -0
  157. package/library/skills/summer-parties/SKILL.md +14 -0
  158. package/library/skills/summer-player-data/SKILL.md +14 -0
  159. package/library/skills/summer-store/SKILL.md +15 -0
  160. package/library/skills/summer-world-chat/SKILL.md +14 -0
  161. package/library/skills/summer-world-saves/SKILL.md +14 -0
  162. package/library/skills/text-to-motion/SKILL.md +15 -0
  163. package/library/skills/tileable-texture/SKILL.md +18 -0
  164. package/library/skills/trailer-shot/SKILL.md +18 -0
  165. package/library/skills/tune-performance/SKILL.md +26 -0
  166. package/library/skills/ui-basics/SKILL.md +14 -0
  167. package/library/skills/ui-graphics/SKILL.md +17 -0
  168. package/library/skills/use-widget-asset/SKILL.md +14 -0
  169. package/library/skills/using-summer/SKILL.md +26 -0
  170. package/library/skills/using-summer/resource.yaml +2 -1
  171. package/library/skills/vehicle-model/SKILL.md +27 -0
  172. package/library/skills/verifying-scenes/SKILL.md +29 -0
  173. package/library/skills/vfx-dissolve/SKILL.md +19 -0
  174. package/library/skills/vfx-fire/SKILL.md +26 -0
  175. package/library/skills/vfx-hit-spark/SKILL.md +23 -0
  176. package/library/skills/vfx-lightning/SKILL.md +23 -0
  177. package/library/skills/vfx-magic-glow/SKILL.md +23 -0
  178. package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
  179. package/library/skills/vfx-smoke/SKILL.md +26 -0
  180. package/library/skills/vfx-water-ripple/SKILL.md +23 -0
  181. package/library/skills/voice-line/SKILL.md +16 -0
  182. package/library/skills/web-and-touch-input/SKILL.md +19 -0
  183. package/library/skills/web-compatibility-look/SKILL.md +20 -0
  184. package/library/skills/world-building-3d/SKILL.md +28 -0
  185. package/package.json +3 -2
  186. package/registry/generated/counts.json +2 -2
  187. package/registry/generated/gemini-extension.json +3 -2
  188. package/registry/generated/index.json +157 -118
  189. package/registry/generated/marketplace.claude.json +2 -2
  190. package/registry/generated/plugin.claude-directory.json +168 -0
  191. package/registry/generated/plugin.claude.json +3 -2
  192. package/registry/generated/plugin.codex.json +15 -11
  193. package/registry/generated/plugin.cursor.json +5 -3
  194. package/registry/generated/plugin.factory.json +3 -2
  195. package/registry/generated/server.json +11 -2
  196. package/registry/generated/skills-index.md +5 -4
  197. package/registry/generated/skills-registry.json +16 -2
  198. package/registry/operations/legacy-names.json +35 -0
  199. package/registry/operations/public-catalog.json +33160 -0
@@ -1,7 +1,8 @@
1
1
  import { Command } from "commander";
2
+ import { CLI_AREAS } from "../../core/ops/areas.js";
2
3
  import { localEntries, localInputSchema, localWireForm, missingInput } from "../../core/ops/catalog.js";
3
- import { answerOfHosted, callHostedV4, hostedCallFailure, searchEverywhere } from "../../core/ops/cloud.js";
4
- import { errorAnswer, isLocalOp, localToolsFor, opFromCli, OP_NAME } from "../../core/ops/contract.js";
4
+ import { answerOfHosted, callHostedV4, describeFromShipped, hostedCallFailure, searchEverywhere } from "../../core/ops/cloud.js";
5
+ import { errorAnswer, isLocalOp, localToolsFor, opFromCli, OP_NAME, resolveOpName } from "../../core/ops/contract.js";
5
6
  import { ENGINE_SPLIT_OPS, engineOpDef, splitToolArgs } from "../../core/ops/engine.js";
6
7
  import { engineFailure, engineNotRunning } from "../../core/ops/engine-answer.js";
7
8
  import { loadLibraryIndex } from "../../core/library-search.js";
@@ -18,12 +19,7 @@ import { dispatchTool, EngineUnavailableError, ToolResultError } from "../../cor
18
19
  * `summer tool` runs); cloud operations run on the hosted Summer server
19
20
  * through its summer_call, with the person's sign-in.
20
21
  */
21
- /** Areas with operations today; CLI command words stay out of this list. */
22
- export const CLI_AREAS = [
23
- "team", "profile", "docs", "assets", "makers", "jobs", "projects", "plans", "games", "store", "jams", "pricing",
24
- "analytics", "experiments", "marketing", "players", "economy", "inbox", "apps", "studio", "chats", "stories",
25
- "plugins", "engine", "account",
26
- ];
22
+ export { CLI_AREAS };
27
23
  const RESERVED_FLAGS = new Set(["json", "dry-run", "wait", "idempotency-key", "input"]);
28
24
  const camel = (flag) => flag.replace(/-([a-z0-9])/g, (_match, letter) => letter.toUpperCase());
29
25
  /** A flag's field: camelCase (`--scene-path` is scenePath), or snake_case when the schema names it so (`--look-at` is look_at). */
@@ -95,6 +91,9 @@ export function parseCallFlags(argv, schema) {
95
91
  }
96
92
  }
97
93
  out.input = { ...out.input, ...fields };
94
+ // --dry-run anywhere makes a dry run, whatever the input says: allow rules for dry runs rely on it (installer/cli-permissions.ts).
95
+ if (out.dryRun)
96
+ delete out.input.dryRun;
98
97
  return out;
99
98
  }
100
99
  export const defaultOpsCliDeps = {
@@ -115,18 +114,21 @@ async function runLocal(op, call, deps) {
115
114
  const tool = split?.tool ?? entry?.mcpTool ?? localToolsFor(op)[0];
116
115
  if (!tool)
117
116
  return errorAnswer("not_found", `No operation is named "${op}".`, { next: { op: "ops.search", input: { query: op } } });
118
- const args = split ? splitToolArgs(split, call.input) : call.input;
117
+ // maxCharacters cuts the text an MCP client gets; the CLI prints the whole answer.
118
+ const { maxCharacters, ...input } = call.input;
119
+ const uncut = maxCharacters === undefined ? undefined : ["maxCharacters: the CLI prints the whole answer; cut it in the shell (for example | head -c 8000)."];
120
+ const args = split ? splitToolArgs(split, input) : input;
119
121
  const def = engineOpDef(op);
120
122
  const guide = guideOf(tool);
121
123
  const describe = guide && def ? { op: "docs.read", input: { id: guide } } : { op: "ops.describe", input: { name: op } };
122
- const missing = missingInput(localInputSchema(op), call.input);
124
+ const missing = missingInput(localInputSchema(op), input);
123
125
  if (missing)
124
126
  return errorAnswer("invalid_input", missing, { next: describe });
125
127
  if (call.dryRun) {
126
128
  return { ok: true, status: "dry_run", preview: { summary: `${entry?.title ?? op}: the input is valid. Nothing was done.`, changes: [], approval: entry?.approval ?? "none", estimate: null, valid: true } };
127
129
  }
128
130
  try {
129
- return { ok: true, status: "done", result: await deps.dispatch(tool, args) };
131
+ return { ok: true, status: "done", result: await deps.dispatch(tool, args), ...(uncut ? { warnings: uncut } : {}) };
130
132
  }
131
133
  catch (error) {
132
134
  if (error instanceof EngineUnavailableError)
@@ -249,6 +251,44 @@ export async function runOpFromCli(op, argv, deps = defaultOpsCliDeps) {
249
251
  deps.printError(`Warning: ${warning}`);
250
252
  return answer.ok ? 0 : 1;
251
253
  }
254
+ /**
255
+ * `summer ops search <words...>`: every operation, local and cloud, signed in
256
+ * or not; a cloud one that needs a sign-in says so. A how-to (docs.search
257
+ * first) points at the library.
258
+ */
259
+ export async function searchFromCli(query, options, deps = defaultOpsCliDeps) {
260
+ const { items, warning } = await searchEverywhere(query, { area: options.area, limit: options.limit }, deps.hosted);
261
+ if (options.json) {
262
+ deps.print(JSON.stringify({ ok: true, status: "done", result: { items }, ...(warning ? { warnings: [warning] } : {}) }));
263
+ return;
264
+ }
265
+ for (const item of items)
266
+ deps.print(`${item.name.padEnd(32)} ${item.effect.padEnd(13)} ${item.title}${item.needsSignIn ? " (needs sign-in)" : ""}`);
267
+ if (items[0]?.name === "docs.search")
268
+ deps.print(`How-to guides for this: summer docs search --query ${shellWord(query)}`);
269
+ if (warning)
270
+ deps.printError(warning);
271
+ }
272
+ /**
273
+ * `summer ops describe <op>`: a local operation from this package; a cloud
274
+ * one from Summer's server, or from the shipped catalog when the server
275
+ * cannot answer (signed out, offline).
276
+ */
277
+ export async function describeFromCli(op, deps = defaultOpsCliDeps, options = {}) {
278
+ const local = localWireForm(resolveOpName(op));
279
+ if (local) {
280
+ // --json: the same answer shape as the cloud describe ({operation}).
281
+ deps.print(options.json ? JSON.stringify({ ok: true, status: "done", result: { operation: local } }) : JSON.stringify(local, null, 2));
282
+ return 0;
283
+ }
284
+ const asked = await runCloud("ops.describe", { input: { name: op }, json: options.json === true, dryRun: false, wait: false }, deps);
285
+ const answer = describeFromShipped(op, asked) ?? asked;
286
+ (answer.ok ? deps.print : deps.printError)(options.json ? JSON.stringify(answer) : formatAnswer(answer));
287
+ if (!options.json && answer.ok)
288
+ for (const warning of answer.warnings ?? [])
289
+ deps.printError(`Warning: ${warning}`);
290
+ return answer.ok ? 0 : 1;
291
+ }
252
292
  function areaCommand(area) {
253
293
  return new Command(area)
254
294
  .description(`Summer ${area} operations: summer ${area} <action> --field value. Find them: summer ops search ${area}`)
@@ -276,26 +316,14 @@ export function opsCliCommands() {
276
316
  .option("--limit <n>", "Most results", "8")
277
317
  .option("--json", "Print the answer as JSON")
278
318
  .action(async (wordsArg, options) => {
279
- const { items, warning } = await searchEverywhere(wordsArg.join(" "), { area: options.area, limit: Number(options.limit) || 8 }, defaultOpsCliDeps.hosted);
280
- if (options.json) {
281
- defaultOpsCliDeps.print(JSON.stringify({ ok: true, status: "done", result: { items }, ...(warning ? { warnings: [warning] } : {}) }));
282
- return;
283
- }
284
- for (const item of items)
285
- defaultOpsCliDeps.print(`${item.name.padEnd(32)} ${item.effect.padEnd(13)} ${item.title}`);
286
- if (warning)
287
- defaultOpsCliDeps.printError(warning);
319
+ await searchFromCli(wordsArg.join(" "), { area: options.area, limit: Number(options.limit) || 8, json: options.json === true });
288
320
  });
289
321
  ops
290
322
  .command("describe <op>")
291
323
  .description("Show one operation with its exact input")
292
- .action(async (op) => {
293
- const local = localWireForm(op);
294
- if (local) {
295
- defaultOpsCliDeps.print(JSON.stringify(local, null, 2));
296
- return;
297
- }
298
- process.exitCode = await runOpFromCli("ops.describe", ["--name", op]);
324
+ .option("--json", "Print the answer as JSON")
325
+ .action(async (op, options) => {
326
+ process.exitCode = await describeFromCli(op, defaultOpsCliDeps, { json: options.json === true });
299
327
  });
300
328
  return [whoami, ops, ...CLI_AREAS.map(areaCommand)];
301
329
  }
@@ -1,4 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  export declare const setupCommand: Command;
3
+ /** True when `path` sits in a git repository that does not ignore it (false outside git, or without git). */
4
+ export declare function notGitIgnored(path: string, cwd?: string): boolean;
3
5
  /** One line instead of a row per three skills: the first few names and how many more. `--json` keeps the full list. */
4
6
  export declare function skillNamesSample(names: readonly string[], max?: number): string;
@@ -1,19 +1,31 @@
1
+ import { spawnSync } from "child_process";
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { relative } from "path";
1
4
  import { Command, Option } from "commander";
2
5
  import { configureAgentMcp, configureSummerGamesMcp, resolveAgentSelection, resolveConfigScope, supportedAgents, } from "../../installer/agent-config.js";
3
6
  import { previewNote, setupSkills } from "../../installer/setup.js";
4
7
  import { printDoctorResult, runDoctor } from "../../core/capabilities/doctor.js";
5
8
  import { brandLine, c, sym, tildeify } from "../../core/format.js";
6
9
  import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
7
- import { resolveDefaultChannel } from "../../installer/version-check.js";
8
- import { agentLabel } from "../../installer/agent-table.js";
10
+ import { listsSummerEngine, resolveDefaultChannel } from "../../installer/version-check.js";
11
+ import { agentLabel, agentSpec, cliSetupRefusal, defaultPathContext } from "../../installer/agent-table.js";
12
+ import { addAllowRules, allowRulesPath, removeAllowRules, supportsAllowRules } from "../../installer/cli-permissions.js";
9
13
  import { PROFILES, parseProfile } from "../../core/tool-profiles.js";
14
+ import { findSetupProjectRoot, writeAgentInstructions } from "../../project-memory/agent-instructions.js";
10
15
  const PROFILE_NOTES = {
11
16
  full: "Every Summer tool.",
12
17
  engine: "Engine, generation and store tools. Every tool: summer setup <agent> --profile full",
13
18
  assets: "Generation and asset tools, no engine tools. Every tool: summer setup <agent> --profile full",
14
19
  };
20
+ /** The options that only shape the MCP entry, which --cli does not write. */
21
+ const MCP_ENTRY_FLAGS = [
22
+ ["print", "--print"],
23
+ ["localDev", "--local-dev"],
24
+ ["channel", "--channel"],
25
+ ["profile", "--profile"],
26
+ ];
15
27
  export const setupCommand = new Command("setup")
16
- .description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games).")
28
+ .description("Configure Summer Engine for an AI agent and run diagnostics. For agents that can sign in to it, it also adds Summer Games (summer-games). --cli installs the skills only: the agent runs summer commands in its shell, with no MCP server.")
17
29
  .argument("[agent]", `Agent to configure: ${supportedAgents.join(", ")}`)
18
30
  .option("--agent <agent>", `Agent to configure: ${supportedAgents.join(", ")}`)
19
31
  .option("--scope <scope>", "Configuration scope: user or project", "user")
@@ -25,6 +37,11 @@ export const setupCommand = new Command("setup")
25
37
  .option("--no-games", "Do not add the Summer Games MCP (summer-games: friends, parties and messages as you, signed in through summer.games)")
26
38
  .option("--profile <profile>", "MCP tool profile: full (every tool, the default), engine (engine, generation and the store; under 128 tools; the default for VS Code Copilot), assets (generation and the asset library, no engine tools)")
27
39
  .option("--assets-only", "Set up for making assets without Summer Engine: the assets profile, only the asset skills, and no Summer Games")
40
+ .option("--cli", "CLI only: install the skills and no MCP server (and no Summer Games). The agent runs Summer as summer <area> <action> commands in its shell; the summer-cli skill teaches it")
41
+ .option("--no-mcp", "Same as --cli")
42
+ .option("--allow-rules", "With --cli, for Claude Code and Codex: let the agent run read-only summer commands, and dry runs of spends, without asking. Prints every rule; written only with --yes")
43
+ .option("--remove-allow-rules", "Remove the allow rules --allow-rules added (Claude Code, Codex), and nothing else")
44
+ .option("--no-project-files", "Inside a Summer project, do not add the Summer instructions to its AGENTS.md and CLAUDE.md (added with --yes by default)")
28
45
  .option("--json", "Print setup result as JSON")
29
46
  .option("--force", "Overwrite existing skill content (passes --force through to skills install)")
30
47
  .option("--recommended", "Install only the recommended skill subset instead of the whole library")
@@ -34,6 +51,17 @@ export const setupCommand = new Command("setup")
34
51
  .action(async (agentArg, opts) => {
35
52
  const agent = resolveAgentSelection(agentArg, opts.agent);
36
53
  const scope = resolveConfigScope(opts.scope);
54
+ if (opts.removeAllowRules) {
55
+ await removeCliAllowRules(agent, scope, opts);
56
+ return;
57
+ }
58
+ if (opts.cli || opts.mcp === false) {
59
+ await setupCliOnly(agent, scope, opts);
60
+ return;
61
+ }
62
+ // On stderr, so --json output stays one JSON document.
63
+ if (opts.allowRules)
64
+ console.error(` ${sym.warn()} ${c.yellow("--allow-rules works with --cli only (the MCP server asks through the agent's own tool approvals); no rules were added.")}`);
37
65
  const profile = resolveSetupProfile(opts);
38
66
  const defaultChannel = await resolveDefaultChannel(cliVersion);
39
67
  const config = await configureAgentMcp({
@@ -60,6 +88,7 @@ export const setupCommand = new Command("setup")
60
88
  stableOnly: Boolean(opts.stableOnly),
61
89
  assetsOnly: Boolean(opts.assetsOnly),
62
90
  });
91
+ const projectFiles = setupProjectInstructions(opts);
63
92
  const doctor = await runDoctor({ quiet: true });
64
93
  if (opts.json) {
65
94
  console.log(JSON.stringify({
@@ -67,16 +96,184 @@ export const setupCommand = new Command("setup")
67
96
  config,
68
97
  games,
69
98
  skills,
99
+ projectFiles,
70
100
  doctor,
71
101
  }, null, 2));
72
102
  }
73
103
  else {
74
- printSetupResult(config, games, skills, doctor);
104
+ printSetupResult(config, games, skills, doctor, projectFiles);
75
105
  }
76
106
  if (!doctor.ok || skills.status === "failed") {
77
107
  process.exit(1);
78
108
  }
79
109
  });
110
+ /**
111
+ * `summer setup <agent> --cli`: the skills (the asset skills with --assets-only)
112
+ * and no MCP server, so the agent runs Summer as `summer <area> <action>` in its
113
+ * shell. Writes no agent config file; an existing summer-engine entry from an
114
+ * earlier setup is left as it is and named in a warning.
115
+ */
116
+ async function setupCliOnly(agent, scope, opts) {
117
+ const spec = agentSpec(agent);
118
+ const mcpFlags = MCP_ENTRY_FLAGS.filter(([key]) => opts[key] !== undefined && opts[key] !== false).map(([, flag]) => flag);
119
+ if (mcpFlags.length > 0) {
120
+ throw new Error(`${mcpFlags.join(", ")} sets up the MCP entry, and --cli writes none. Leave ${mcpFlags.length === 1 ? "it" : "them"} out, or leave out --cli.`);
121
+ }
122
+ const refusal = cliSetupRefusal(spec);
123
+ if (refusal)
124
+ throw new Error(refusal);
125
+ const warnings = spec.status === "legacy" && spec.legacyNote ? [spec.legacyNote] : [];
126
+ const existing = await recordedMcpEntry(agent, scope);
127
+ if (existing) {
128
+ warnings.push(`${tildeify(existing)} still lists the summer-engine MCP server from an earlier setup. Remove that entry to use the CLI only.`);
129
+ }
130
+ const dryRun = Boolean(opts.dryRun);
131
+ const skills = setupSkills(agent, {
132
+ dryRun,
133
+ yes: Boolean(opts.yes),
134
+ force: Boolean(opts.force),
135
+ scope,
136
+ recommended: Boolean(opts.recommended),
137
+ stableOnly: Boolean(opts.stableOnly),
138
+ assetsOnly: Boolean(opts.assetsOnly),
139
+ commands: false,
140
+ });
141
+ // Read-only and dry-run summer commands run without asking, where the agent has clean allow rules.
142
+ // Opt-in: the flag is in the command the person approves.
143
+ let allowRules = null;
144
+ if (opts.allowRules && !supportsAllowRules(agent)) {
145
+ warnings.push(`--allow-rules is for Claude Code and Codex; ${agentLabel(agent)} manages command approvals in its own settings.`);
146
+ }
147
+ else if (opts.allowRules) {
148
+ try {
149
+ allowRules = await addAllowRules(agent, allowRulesPath(agent, scope, defaultPathContext()), Boolean(opts.yes) && !dryRun);
150
+ const shared = agent === "claude-code" && scope === "project" ? notGitIgnored(allowRules.path) : false;
151
+ if (shared)
152
+ warnings.push(`${tildeify(allowRules.path)} is not in .gitignore, so git would share these personal allow rules. Add .claude/settings.local.json to .gitignore.`);
153
+ }
154
+ catch (error) {
155
+ warnings.push(error instanceof Error ? error.message : String(error));
156
+ }
157
+ }
158
+ const projectFiles = setupProjectInstructions(opts);
159
+ const doctor = await runDoctor({ quiet: true });
160
+ const ok = doctor.ok && skills.status !== "failed";
161
+ if (opts.json) {
162
+ console.log(JSON.stringify({ ok, mcp: false, agent, scope, config: null, games: null, existingMcpEntry: existing, skills, allowRules, projectFiles, doctor, warnings }, null, 2));
163
+ }
164
+ else {
165
+ const label = agentLabel(agent);
166
+ console.log("");
167
+ console.log(brandLine(cliVersion));
168
+ console.log("");
169
+ console.log(` ${dryRun ? c.dim("(dry run)") : sym.ok()} ${c.bold(`CLI only for ${label}`)}: no MCP server`);
170
+ console.log(` ${c.dim("The agent runs Summer in its shell: summer <area> <action>, or npx -y summer-engine@latest <area> <action>. The summer-cli skill teaches it.")}`);
171
+ printSkillsLine(skills, dryRun);
172
+ if (allowRules)
173
+ printAllowRules(allowRules, label, Boolean(opts.yes) && !dryRun);
174
+ else if (supportsAllowRules(agent) && !opts.allowRules) {
175
+ console.log(` ${sym.arrow()} ${c.dim(`${label} asks before each summer command. To run read-only and dry-run ones without asking: summer setup ${agent} --cli --allow-rules --yes (it prints every rule).`)}`);
176
+ }
177
+ printProjectFiles(projectFiles);
178
+ for (const warning of warnings)
179
+ console.log(` ${sym.warn()} ${c.yellow(warning)}`);
180
+ console.log("");
181
+ printDoctorResult(doctor);
182
+ if (ok && !dryRun)
183
+ printTryIt(label, Boolean(opts.assetsOnly));
184
+ }
185
+ if (!ok)
186
+ process.exit(1);
187
+ }
188
+ /** The allow rules, each on its line: what the agent now runs without asking, or would with --yes. */
189
+ function printAllowRules(result, label, write) {
190
+ const where = c.dim(tildeify(result.path));
191
+ if (!write) {
192
+ console.log(` ${sym.arrow()} ${label} would run these summer commands without asking. Add them: run the same command with --yes. ${where}`);
193
+ }
194
+ else if (result.changed.length === 0) {
195
+ console.log(` ${sym.ok()} ${label} already runs read-only and dry-run summer commands without asking ${where}`);
196
+ return;
197
+ }
198
+ else {
199
+ console.log(` ${sym.ok()} ${label} runs these summer commands without asking (${result.changed.length} rules added) ${where}`);
200
+ }
201
+ for (const rule of result.rules)
202
+ console.log(` ${c.dim(rule)}`);
203
+ console.log(` ${c.dim("Changes, publishing, prices, deletes and messages still ask. jobs wait and assets get save the files they name. Put --dry-run right after the action: summer assets generate-image --dry-run --prompt ... Undo: --remove-allow-rules")}`);
204
+ }
205
+ /** True when `path` sits in a git repository that does not ignore it (false outside git, or without git). */
206
+ export function notGitIgnored(path, cwd = process.cwd()) {
207
+ const run = (args) => spawnSync("git", args, { cwd, stdio: "ignore" }).status;
208
+ if (run(["rev-parse", "--is-inside-work-tree"]) !== 0)
209
+ return false;
210
+ return run(["check-ignore", "-q", relative(cwd, path)]) === 1;
211
+ }
212
+ /** `summer setup <agent> --remove-allow-rules`: take out exactly the rules --allow-rules added. */
213
+ async function removeCliAllowRules(agent, scope, opts) {
214
+ if (!supportsAllowRules(agent))
215
+ throw new Error(`--remove-allow-rules is for Claude Code and Codex; Summer adds no allow rules for ${agentLabel(agent)}.`);
216
+ const result = await removeAllowRules(agent, allowRulesPath(agent, scope, defaultPathContext()), !opts.dryRun);
217
+ if (opts.json) {
218
+ console.log(JSON.stringify({ ok: true, agent, scope, allowRules: result }, null, 2));
219
+ return;
220
+ }
221
+ const where = c.dim(tildeify(result.path));
222
+ if (result.changed.length === 0)
223
+ console.log(` ${sym.ok()} No Summer allow rules for ${agentLabel(agent)} ${where}`);
224
+ else
225
+ console.log(` ${opts.dryRun ? c.dim("(dry run)") : sym.ok()} ${opts.dryRun ? "Would remove" : "Removed"} ${result.changed.length} Summer allow rules for ${agentLabel(agent)} ${where}`);
226
+ }
227
+ /**
228
+ * Inside a Summer project (findSetupProjectRoot: project.godot at or above the
229
+ * working directory, no higher than the git top level, never home): its
230
+ * AGENTS.md and CLAUDE.md, added when missing and never overwritten
231
+ * (project-memory/agent-instructions.ts). Like the skills, they are only
232
+ * planned without --yes. Null outside a project, with --no-project-files,
233
+ * --print, and --assets-only (the block is about building in Summer Engine).
234
+ */
235
+ function setupProjectInstructions(opts) {
236
+ if (opts.projectFiles === false || opts.print || opts.assetsOnly)
237
+ return null;
238
+ const root = findSetupProjectRoot();
239
+ if (!root)
240
+ return null;
241
+ const dryRun = Boolean(opts.dryRun);
242
+ const written = Boolean(opts.yes) && !dryRun;
243
+ return { root, written, dryRun, files: writeAgentInstructions(root, { dryRun: !written }) };
244
+ }
245
+ function printProjectFiles(project) {
246
+ if (!project)
247
+ return;
248
+ const changed = project.files.filter((file) => file.action === "created" || file.action === "added" || file.action === "updated");
249
+ if (changed.length) {
250
+ const where = c.dim(changed.map((file) => tildeify(file.path)).join(", "));
251
+ if (project.written)
252
+ console.log(` ${sym.ok()} Added the Summer instructions for agents ${where}`);
253
+ else if (project.dryRun)
254
+ console.log(` ${c.dim("(dry run)")} Would add the Summer instructions for agents ${where}`);
255
+ else
256
+ console.log(` ${sym.warn()} Instructions for agents not added. Run the same setup command again with --yes to add them ${where}`);
257
+ }
258
+ for (const file of project.files) {
259
+ if (!file.reason)
260
+ continue;
261
+ const what = file.action === "skipped" ? "not written" : "left as it is";
262
+ console.log(` ${sym.warn()} ${c.yellow(`${tildeify(file.path)} ${what}: ${file.reason}`)}`);
263
+ }
264
+ }
265
+ /** The config file that already lists summer-engine for this agent and scope, or null. */
266
+ async function recordedMcpEntry(agent, scope) {
267
+ try {
268
+ const target = await configureAgentMcp({ agent, scope, print: true });
269
+ if (!existsSync(target.path))
270
+ return null;
271
+ return listsSummerEngine(readFileSync(target.path, "utf-8"), target.format) ? target.path : null;
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
80
277
  /** --assets-only means the assets profile; --profile must name a profile and agree with it. */
81
278
  function resolveSetupProfile(opts) {
82
279
  const profile = opts.profile === undefined ? undefined : parseProfile(opts.profile);
@@ -90,7 +287,7 @@ function resolveSetupProfile(opts) {
90
287
  }
91
288
  return profile;
92
289
  }
93
- function printSetupResult(config, games, skills, doctor) {
290
+ function printSetupResult(config, games, skills, doctor, projectFiles) {
94
291
  if (config.print) {
95
292
  console.log(config.snippet.trimEnd());
96
293
  return;
@@ -132,6 +329,22 @@ function printSetupResult(config, games, skills, doctor) {
132
329
  if (!config.dryRun)
133
330
  console.log(` ${c.dim(`Sign in once: ${games.signIn}`)}`);
134
331
  }
332
+ printSkillsLine(skills, config.dryRun);
333
+ printProjectFiles(projectFiles);
334
+ for (const warning of config.warnings) {
335
+ console.log(` ${sym.warn()} ${c.yellow(warning)}`);
336
+ }
337
+ console.log("");
338
+ printDoctorResult(doctor);
339
+ if (doctor.ok && skills.status !== "failed" && !config.dryRun)
340
+ printTryIt(label, config.profile === "assets");
341
+ }
342
+ function printTryIt(label, assets) {
343
+ console.log("");
344
+ const ask = assets ? "make a sword clash sound effect" : "add a DirectionalLight3D and Camera3D to my scene";
345
+ console.log(`${c.dim("Try it:")} open ${label} and ask: ${c.bold(`"${ask}"`)}`);
346
+ }
347
+ function printSkillsLine(skills, dryRun) {
135
348
  if (skills.status === "installed") {
136
349
  const installed = parseInstalledSkills(skills.stdout);
137
350
  if (installed.length > 0) {
@@ -147,7 +360,7 @@ function printSetupResult(config, games, skills, doctor) {
147
360
  console.log(` ${sym.ok()} ${skills.message}`);
148
361
  }
149
362
  }
150
- else if (skills.status === "planned" && config.dryRun) {
363
+ else if (skills.status === "planned" && dryRun) {
151
364
  const note = previewNote(skills);
152
365
  console.log(` ${c.dim("(dry run)")} Would install ${c.bold(String(skills.count ?? "?") + " skills")}${note ? c.dim(` (${note})`) : ""} ${c.dim(skills.destination ?? "")}`);
153
366
  }
@@ -159,16 +372,6 @@ function printSetupResult(config, games, skills, doctor) {
159
372
  if (skills.stderr)
160
373
  console.log(` ${c.dim(skills.stderr)}`);
161
374
  }
162
- for (const warning of config.warnings) {
163
- console.log(` ${sym.warn()} ${c.yellow(warning)}`);
164
- }
165
- console.log("");
166
- printDoctorResult(doctor);
167
- if (doctor.ok && skills.status !== "failed" && !config.dryRun) {
168
- console.log("");
169
- const ask = config.profile === "assets" ? "make a sword clash sound effect" : "add a DirectionalLight3D and Camera3D to my scene";
170
- console.log(`${c.dim("Try it:")} open ${label} and ask: ${c.bold(`"${ask}"`)}`);
171
- }
172
375
  }
173
376
  /** One line instead of a row per three skills: the first few names and how many more. `--json` keeps the full list. */
174
377
  export function skillNamesSample(names, max = 8) {
@@ -234,8 +234,13 @@ skillsCommand
234
234
  .command("list")
235
235
  .description("List available skills")
236
236
  .option("--by-domain", "Group skills by their primary domain (same grouping as library/skills/README.md)")
237
+ .option("--json", "Print the skills as JSON")
237
238
  .action((opts) => {
238
239
  const skills = getBuiltinSkills();
240
+ if (opts.json) {
241
+ console.log(JSON.stringify({ skills }));
242
+ return;
243
+ }
239
244
  if (skills.length === 0) {
240
245
  console.log("No skills found.");
241
246
  return;
@@ -287,6 +292,7 @@ skillsCommand
287
292
  // Pre-reversal opt-in, kept one release as a hidden no-op so old scripts still parse.
288
293
  .addOption(new Option("--include-preview", "No-op: preview skills install by default").hideHelp())
289
294
  .option("--force", "Overwrite existing skill files (wipe stale skill dirs before copying)")
295
+ .option("--no-commands", "Claude Code: skip the slash commands (/summer, /gameskill, /start), which start work through the MCP tools")
290
296
  .action((name, opts) => {
291
297
  const agent = orDie(() => resolveSkillAgent(opts));
292
298
  const scope = orDie(() => resolveSkillScope(agent, opts));
@@ -344,15 +350,16 @@ skillsCommand
344
350
  console.log(previewSkippedLine(previewSkipped));
345
351
  // Claude Code: also install slash commands (`commands/*.md`)
346
352
  // into `~/.claude/commands/`. Other agents don't have an equivalent today.
347
- // The commands start and route game work in the engine, so the asset
348
- // skills (--assets, setup --assets-only) leave them out.
349
- if (agent === "claude-code" && !opts.assets) {
353
+ // The commands start and route game work in the engine through the MCP
354
+ // tools, so the asset skills (--assets, setup --assets-only) and a setup
355
+ // without MCP (--no-commands, setup --cli) leave them out.
356
+ if (agent === "claude-code" && !opts.assets && opts.commands !== false) {
350
357
  const commandResults = installClaudeCommands(scope, Boolean(opts.force));
351
358
  for (const r of commandResults) {
352
359
  console.log(` ${r.action} command ${r.name} -> ${r.path}`);
353
360
  }
354
361
  }
355
- writeMarkerForLocation(location);
362
+ writeMarkerForLocation(location, skills.map((skill) => skill.name));
356
363
  printInstallSummary(skills.length, agent, scope, location);
357
364
  });
358
365
  /**
@@ -390,12 +397,13 @@ function installClaudeCommands(scope, force) {
390
397
  * `skills-version-stale` check can detect drift later. Devin Desktop (formerly Windsurf) writes a
391
398
  * single rules file with no surrounding dir, so we skip it there.
392
399
  */
393
- function writeMarkerForLocation(location) {
400
+ function writeMarkerForLocation(location, installed) {
394
401
  if (location.kind === "windsurf-rule-file")
395
402
  return;
396
403
  try {
397
404
  mkdirSync(location.path, { recursive: true });
398
- writeSkillMarker(location.path, cliVersion);
405
+ // Skills the upgrade refresh left stay protected, unless this install just replaced them.
406
+ writeSkillMarker(location.path, cliVersion, new Date(), installed);
399
407
  }
400
408
  catch {
401
409
  // Marker is purely informational. If the FS rejects it (e.g. permission
@@ -405,7 +413,8 @@ function writeMarkerForLocation(location) {
405
413
  skillsCommand
406
414
  .command("info <name>")
407
415
  .description("Show skill description and preview")
408
- .action((name) => {
416
+ .option("--json", "Print the skill and its full SKILL.md body as JSON")
417
+ .action((name, opts) => {
409
418
  const meta = getSkillMeta(name);
410
419
  if (!meta) {
411
420
  console.error(`Unknown skill: ${name}`);
@@ -416,6 +425,10 @@ skillsCommand
416
425
  die(`Skill files missing: ${name}`);
417
426
  const skillPath = join(src, "SKILL.md");
418
427
  const content = readFileSync(skillPath, "utf-8");
428
+ if (opts.json) {
429
+ console.log(JSON.stringify({ ...meta, file: skillPath, body: content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, "") }));
430
+ return;
431
+ }
419
432
  console.log(`\n${meta.name}`);
420
433
  console.log("-".repeat(40));
421
434
  console.log(meta.description);
@@ -1,3 +1,29 @@
1
1
  import { Command } from "commander";
2
2
  export declare const statusCommand: Command;
3
+ /** What `summer status` reports; `--json` prints exactly this. */
4
+ export interface StatusReport {
5
+ auth: {
6
+ signedIn: false;
7
+ loginUrl: string;
8
+ } | {
9
+ signedIn: true;
10
+ source: "env";
11
+ } | {
12
+ signedIn: true;
13
+ source: "stored";
14
+ email?: string;
15
+ identityError?: string;
16
+ };
17
+ engine: {
18
+ state: "not_running";
19
+ } | {
20
+ state: "not_responding";
21
+ port: number;
22
+ } | {
23
+ state: "running";
24
+ port: number;
25
+ version: string;
26
+ };
27
+ }
28
+ export declare function readStatus(): Promise<StatusReport>;
3
29
  export declare function runStatus(log: (message: string) => void): Promise<void>;
@@ -4,52 +4,67 @@ import { resolveGatewayUrl } from "../../core/config.js";
4
4
  import { getApiToken, getApiPort, checkEngineHealth } from "../../core/engine.js";
5
5
  export const statusCommand = new Command("status")
6
6
  .description("Check Summer Engine status, connection, and auth state")
7
- .action(async () => {
8
- await runStatus(console.log);
7
+ .option("--json", "Print the status as JSON")
8
+ .action(async (opts) => {
9
+ if (opts.json)
10
+ console.log(JSON.stringify(await readStatus()));
11
+ else
12
+ await runStatus(console.log);
9
13
  });
14
+ export async function readStatus() {
15
+ const authToken = await getAuthToken();
16
+ // The env credential is what every gateway call uses; a stored identity
17
+ // (if any) is not the one in effect, so it is not reported.
18
+ const auth = !authToken
19
+ ? { signedIn: false, loginUrl: await loginPageHint() }
20
+ : hasEnvAuthToken()
21
+ ? { signedIn: true, source: "env" }
22
+ : { signedIn: true, source: "stored", ...(await storedIdentity()) };
23
+ if (!(await getApiToken()))
24
+ return { auth, engine: { state: "not_running" } };
25
+ const port = await getApiPort();
26
+ const health = await checkEngineHealth(port);
27
+ // /api/health carries no project name/path/scene (see EngineHealth in
28
+ // core/engine.ts); summer_get_project_context is the read for those.
29
+ return { auth, engine: health ? { state: "running", port, version: health.version } : { state: "not_responding", port } };
30
+ }
10
31
  export async function runStatus(log) {
32
+ const { auth, engine } = await readStatus();
11
33
  log("Summer Engine Status\n");
12
- const authToken = await getAuthToken();
13
- if (!authToken) {
34
+ if (!auth.signedIn) {
14
35
  log(" Auth: Not logged in");
15
36
  log(" Run: npx -y summer-engine@latest login");
16
- log(` Or: ${await loginPageHint()}`);
37
+ log(` Or: ${auth.loginUrl}`);
17
38
  }
18
- else if (hasEnvAuthToken()) {
19
- // The env credential is what every gateway call uses; a stored identity
20
- // (if any) is not the one in effect, so it is not printed.
39
+ else if (auth.source === "env") {
21
40
  log(" Auth: SUMMER_TOKEN (env) — logout does not affect it");
22
41
  }
23
42
  else {
24
- log(` Auth: Logged in${await storedIdentitySuffix()}`);
43
+ const identity = auth.identityError ? ` (identity unreadable: ${auth.identityError})` : auth.email ? ` as ${auth.email}` : "";
44
+ log(` Auth: Logged in${identity}`);
25
45
  }
26
- const apiToken = await getApiToken();
27
- const port = await getApiPort();
28
- if (!apiToken) {
46
+ if (engine.state === "not_running") {
29
47
  log(" Engine: Not running (no api-token found)");
30
48
  log("\n To start: summer run");
31
49
  return;
32
50
  }
33
51
  log(" API Token: Found (~/.summer/api-token)");
34
- log(` Port: ${port}`);
35
- const health = await checkEngineHealth(port);
36
- if (!health) {
52
+ log(` Port: ${engine.port}`);
53
+ if (engine.state === "not_responding") {
37
54
  log(" Engine: Not responding (may have closed since last launch)");
38
55
  log("\n To start: summer run");
39
56
  return;
40
57
  }
41
- // /api/health carries no project name/path/scene (see EngineHealth in
42
- // core/engine.ts); summer_get_project_context is the read for those.
43
- log(` Engine: Running (v${health.version})`);
58
+ log(` Engine: Running (v${engine.version})`);
44
59
  }
45
60
  /** A corrupt user.json must not take `summer status` down with it. */
46
- async function storedIdentitySuffix() {
61
+ async function storedIdentity() {
47
62
  try {
48
63
  const userInfo = await getUserInfo();
49
- return userInfo?.email ? ` as ${userInfo.email}` : "";
64
+ return userInfo?.email ? { email: userInfo.email } : {};
50
65
  }
51
66
  catch (error) {
52
- return ` (identity unreadable: ${error instanceof Error ? error.message : String(error)})`;
67
+ return { identityError: error instanceof Error ? error.message : String(error) };
53
68
  }
54
69
  }
55
70
  async function loginPageHint() {