Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"summer-engine": {
"command": "npx",
"args": [
"summer-engine",
"-y",
"summer-engine@latest",
"mcp"
]
}
Expand Down
7 changes: 2 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Changelog

## Unreleased

- Add optional image background removal to MCP and `summer tool generate-image`, with a shared validated schema and searchable descriptor.

All notable changes to summer-engine will be documented here. Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).

## [3.0.0] (2026-09-09): "The Library"
Expand All @@ -18,7 +14,7 @@ v3 rebuilds the package around one idea: every resource is described once (`libr
### Breaking changes vs 2.8.x
- **Removed commands and tools.** `summer cloud` (and the seven `summer_cloud_*` MCP tools + the `summer-cloud` skill), `summer agent`, `summer logs` / `summer_creator_logs`. Tool count 62 → 86: 54 names unchanged, 8 removed (listed), 32 new. No MCP tool was renamed; no tool argument was renamed.
- **Skill layout.** `skills/<category>/<name>/` became flat `library/skills/<slug>/`; `references/` became `library/references/<slug>/`; `_persona/` is gone. Anything that read skills from the package path (`node_modules/summer-engine/skills/…`) must read `library/skills/`. Plugin-marketplace installs expose `/summer:<slug>` instead of `/summer:<category>/<name>`. Cross-references in prompts using the v2 `summer:<category>/<name>` form do not resolve; the old names are recorded in `registry/generated/aliases.json` but nothing resolves them at runtime yet (only legacy `template-<slug>` names in `summer create` do). **Installed skill snapshots are not refreshed automatically**: run `npx -y summer-engine@latest setup <agent> --yes --force` once (`summer doctor` flags the stale snapshot as `skills-version-stale`).
- **`summer setup <agent>` installs every skill** (95, preview ones labelled) instead of the recommended subset; `--recommended` restores the 2.8.x behaviour, `--stable-only` skips preview skills.
- **`summer setup <agent>` installs every skill** (94, preview ones labelled) instead of the recommended subset; `--recommended` restores the 2.8.x behaviour, `--stable-only` skips preview skills.
- **Templates are pinned.** `summer create <slug>` fetches an exact commit and verifies a tree digest instead of cloning a repository's default branch, and writes `.summer/project.json`; `summer list templates` reads the compiled registry, never a GitHub org listing. Legacy `template-<slug>` names still resolve.
- **Launch and play are quiet by default when an agent drives.** `summer run` launches the engine in the background (no focus steal) whenever stdout is not a TTY and the engine supports it (0.5.66+; older engines launch with focus and say so); `--focus` restores the old behaviour; a human in a terminal still gets focus. `summer_play` no longer switches the editor to the Game tab or grabs focus (`PlayGame agent:true`); `focus: true` restores the toolbar-Play behaviour. `summer run` with no path needs `--no-project` to open a bare editor.
- **Exit codes.** `summer <unknown-command>` exits 1 instead of printing the intro. `summer tool <name>` exits 1 on every result the MCP face marks `isError` (including `engine_lacks_op`).
Expand All @@ -27,6 +23,7 @@ v3 rebuilds the package around one idea: every resource is described once (`libr
- `summer mcp setup <agent>` still works as a deprecated alias of `summer setup <agent>`.

### Added
- Optional image background removal (`removeBackground`) on `summer_generate_image` / `summer tool generate-image`, with a shared validated schema and searchable descriptor.
- **The librarian**: `summer_search_library` (BM25 over the compiled index, optional semantic fusion when an embeddings sidecar exists, lexical-only offline, never throws) returns ranked entries of every kind for a plain-words task description; `summer_read_library` loads one entry by id (a skill's body, a tool's call recipe, a template's pin, a reference's text), ending in the feedback footer (`entry_id@hash`) that `summer_library_feedback` reports against. Both engine-free, both faces (`summer tool search-library` / `read-library`). Preview entries never outrank stable ones on comparable evidence.
- **Navigation**: `summer open <target>` / `summer_open` / `summer tool open` opens the exact summerengine.com page or editor surface by intent (a product-map id such as `billing`, `my-games`, `mcp-guide`, `scene`, `inspector`; an intent phrase; a `res://` path; or a site path), in the browser (through `/login?returnUrl=` when needed) or in the running editor; `--print` resolves without opening, `--list` prints the map. Web rows come from summerengine.com's route catalog (vendored snapshot `assets/navigation/web-routes.json`); editor rows forward to the engine's `Navigate` op (0.5.66+) and fall back to the original ops (`OpenScene`, `SelectNode`, `OpenResource`, `FocusDock`, `RevealInFileSystem`) on 0.5.65. `summer open <project-dir>` is unchanged. Design: `docs/design/NAVIGATION-DESIGN.md`.
- **Launch posture** (`docs/TESTING.md` "Working in the background"): `summer run [--background|--focus]`: background is the default when stdout is not a TTY. The positive gate is a `<engine> --help` probe for `--summer-background` (cached per binary path + mtime in `~/.summer/launch-probe-cache.json`), never a version pre-check, so dev builds still stamped 0.5.65 are detected correctly; once up, `/api/health capabilities.launchPostures` is the authoritative advert and `summer_get_project_context` surfaces it. `summer_play` is quiet by default (`focus: true` opts in) and its result echoes `agent_quiet` or a `posture_note` when the engine predates quiet play.
Expand Down
2 changes: 1 addition & 1 deletion docs/RELEASE-3.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ npx clear-npx-cache && npx -y summer-engine@latest setup <agent> --yes --force
npm view summer-engine dist-tags # expect: latest: 2.8.2, next: 3.0.0
```

4. **Soak.** Dogfood with the real published tarball. The MCP entry has to point at `@next`, otherwise the agent runs 2.8.2's server with 3.0.0's skills:
4. **Soak.** Dogfood with the real published tarball. The MCP entry has to point at `@next`, otherwise the agent runs 2.8.2's server with 3.0.0's skills. `summer setup` from a 3.0.0 CLI does this by default while 3.0.0 is ahead of npm `latest` (it prints a note; `--channel latest` overrides), and `summer doctor` recommends the matching `@next --channel next` refresh:

```bash
npx clear-npx-cache
Expand Down
3 changes: 2 additions & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"summer-engine": {
"command": "npx",
"args": [
"summer-engine",
"-y",
"summer-engine@latest",
"mcp"
],
"cwd": "${extensionPath}"
Expand Down
4 changes: 3 additions & 1 deletion library/skills/navigate-summer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ Never open a browser or switch the editor's tab as a side effect of building. Op
- `action: "engine_not_running"` — nothing opened. Tell the user to start Summer Engine (`summer run <project>`) or open the project in the desktop app, then offer to retry. Do not fall back to editing files.
- `action: "unsupported"` (`failure_reason: engine_lacks_op`) — this Summer Engine build cannot open that surface (it predates the `Navigate` op, or does not advertise that id). Say so plainly, tell the user to update Summer Engine, and describe what to open by hand. Never claim it opened.
- `action: "ambiguous"` — show the top matches by title and ask, or pick the obvious one and say which you picked.
- `action: "not_found"` — the intent is not a Summer destination. Do not invent a URL; the tool only opens summerengine.com and docs.summerengine.com.
- `action: "not_found"` — the intent is not a Summer destination (or a `res://` path tried to escape the project). Do not invent a URL; the tool only opens summerengine.com and its subdomains.
- `action: "open_failed"` — this machine could not launch a browser (headless, no display). Nothing opened; paste the `url` for the user.
- `action: "blocked_origin"` — the configured gateway is not a Summer origin; nothing opened. Tell the user to check `gateway.url` / `SUMMER_GATEWAY_URL`.

## When to hand over a link instead

Expand Down
4 changes: 2 additions & 2 deletions library/skills/using-summer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Do NOT fall back to editing `.tscn` files directly. The engine reads them on dis
If skills aren't found or the MCP server fails to start:

1. Check whether `summer` is on PATH: `which summer` / `where summer`.
2. If not, point them at: `npx -y summer-engine@latest setup <agent> --yes --force`.
2. If not, point them at: `npx -y summer-engine@latest setup <agent> --yes --force` (use `@next` and `--channel next` while a release is soaking on the `next` tag; `summer doctor` prints the right command).
3. If `summer doctor` is available, run it: `summer doctor` reports auth, engine, port, project memory, and skill state.

## When Summer Is Stale
Expand All @@ -150,7 +150,7 @@ Run `summer doctor` early in a fresh Summer session when setup, MCP tools, slash
npx clear-npx-cache && npx -y summer-engine@latest setup <agent> --yes --force
```

Use the real agent slug from doctor or the current environment (`claude-code`, `codex`, `cursor`, `gemini`, `github-copilot`, `vscode-copilot`, `opencode`, etc.).
Use the dist-tag `summer doctor` recommends: `@latest` normally, `@next` (with `--channel next`) when this CLI was installed from the soaking release, otherwise the MCP server silently drops back to the older `latest`. Use the real agent slug from doctor or the current environment (`claude-code`, `codex`, `cursor`, `gemini`, `github-copilot`, `vscode-copilot`, `opencode`, etc.).

Why this exact command matters:

Expand Down
3 changes: 2 additions & 1 deletion registry/generated/gemini-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"summer-engine": {
"command": "npx",
"args": [
"summer-engine",
"-y",
"summer-engine@latest",
"mcp"
],
"cwd": "${extensionPath}"
Expand Down
4 changes: 2 additions & 2 deletions registry/generated/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -1945,7 +1945,7 @@
"id": "skill/navigate-summer",
"kind": "skill",
"version": "1.0.0",
"content_hash": "eec416519ba3c0aa6338a9f75a0287880f5d251d87335c6a4b6acd93229b1dc1",
"content_hash": "aec360ff8c87b41b565457a6464b1dfbee3a1e241d84e1fc67d9a93a42842d5e",
"summary": "When to open a Summer web page or editor surface FOR the user (billing, their games, the scene just built) versus acting through the API, using summer_open.",
"use_when": [
"the user asks to see, check, or decide something — \"show me my billing\", \"where do I change my plan\", \"open my games\", \"let me look at the scene\", \"take me to the MCP guide for Cursor\"",
Expand Down Expand Up @@ -3178,7 +3178,7 @@
"id": "skill/using-summer",
"kind": "skill",
"version": "1.0.4",
"content_hash": "a14fd976977d7cb4b806a5262988979f17a86129fe55375bd9696c67a4bb243c",
"content_hash": "4df2bb87e809a70e8cc165c27997e0060993444d054182ca654fa6380511752d",
"summary": "Session bootstrap for Summer projects — establishes how to find and use Summer skills and the summer-engine MCP before any response.",
"use_when": [
"starting any conversation in a Summer Engine project",
Expand Down
3 changes: 2 additions & 1 deletion registry/generated/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"summer-engine": {
"command": "npx",
"args": [
"summer-engine",
"-y",
"summer-engine@latest",
"mcp"
]
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/generate-registry/generate-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ describe("generateRegistry: manifests (golden shapes)", () => {
expect(gemini.contextFileName).toBe("GEMINI.md");
expect((gemini.mcpServers as Record<string, unknown>)["summer-engine"]).toEqual({
command: "npx",
args: ["summer-engine", "mcp"],
args: ["-y", "summer-engine@latest", "mcp"],
cwd: "${extensionPath}",
});

Expand Down
4 changes: 3 additions & 1 deletion scripts/generate-registry/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ function skillPaths(slugs: string[]): string[] {
* from the generated root `.mcp.json`.
*/
export function bundledMcpServer(): { command: string; args: string[] } {
return { command: "npx", args: ["summer-engine", "mcp"] };
// `-y` so a non-TTY host never hangs on npx's install prompt, `@latest` so a
// stale npx cache never serves an old server (README "Troubleshooting").
return { command: "npx", args: ["-y", "summer-engine@latest", "mcp"] };
}

function buildMcpJson(): string {
Expand Down
23 changes: 23 additions & 0 deletions src/cli/commands/open.navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,26 @@ describe("summer open — navigation results", () => {
expect(text).toMatch(/Update Summer Engine/);
});
});

describe("summer open — branch precedence (release review 2026-09-11)", () => {
it("a known web path is navigation, an unknown absolute path is a project path", () => {
expect(looksLikeProjectPath("/pricing")).toBe(false);
expect(looksLikeProjectPath("/studio?tab=billing")).toBe(false);
expect(looksLikeProjectPath("/nonexistent/dir")).toBe(true);
expect(looksLikeProjectPath(root)).toBe(true);
});

it("an exact map id wins over a same-named directory in cwd; ./name still means the directory", async () => {
const { mkdir } = await import("node:fs/promises");
const prev = process.cwd();
await mkdir(join(root, "billing"));
process.chdir(root);
try {
expect(looksLikeProjectPath("billing")).toBe(false);
expect(looksLikeProjectPath("./billing")).toBe(true);
expect(looksLikeProjectPath("somefolder")).toBe(false);
} finally {
process.chdir(prev);
}
});
});
34 changes: 31 additions & 3 deletions src/cli/commands/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { getAuthToken } from "../../core/auth.js";
import { resolveGatewayUrl } from "../../core/config.js";
import { EngineApiClient } from "../../core/api-client.js";
import {
resolveTarget,
runOpen,
type OpenDeps,
type OpenResult,
type OpenSurface,
} from "../../core/capabilities/navigation/open.js";
import { getNavTarget } from "../../core/capabilities/navigation/targets.js";
import { c, sym } from "../../core/format.js";

/**
Expand Down Expand Up @@ -39,11 +41,32 @@ export interface OpenNavigationOptions {
param?: string[];
}

/** Path-shaped (absolute, relative, home, Windows drive) or an existing directory. */
/**
* Which branch does `summer open <arg>` take?
* - An exact product-map id or alias (`billing`, `inspector`) is ALWAYS a
* navigation target, even if a directory of that name exists in cwd — use
* `./billing` to open a project folder that happens to share a name.
* - `./x`, `../x`, `~/x`, `.`, `..`, Windows drive paths: project directory.
* - `/x`: a project directory when it exists on disk; otherwise a
* summerengine.com path (`/pricing`) when the map knows it; otherwise the
* project-directory branch, which reports "Directory not found".
* - Anything else: a project directory only when it exists as one.
*/
export function looksLikeProjectPath(arg: string): boolean {
if (/^(\/|\.\/|\.\.\/|~)/.test(arg) || /^[A-Za-z]:[\\/]/.test(arg) || arg === "." || arg === "..") return true;
const trimmed = arg.trim();
if (getNavTarget(trimmed.toLowerCase().replace(/\s+/g, "-"))) return false;
if (/^(\.\/|\.\.\/|~)/.test(trimmed) || /^[A-Za-z]:[\\/]/.test(trimmed) || trimmed === "." || trimmed === "..") return true;
if (trimmed.startsWith("/")) {
if (isDirectory(trimmed)) return true;
const resolution = resolveTarget(trimmed, {}, "web");
return !(resolution.kind === "target");
}
return isDirectory(trimmed);
}

function isDirectory(path: string): boolean {
try {
return statSync(resolve(arg)).isDirectory();
return statSync(resolve(path)).isDirectory();
} catch {
return false;
}
Expand Down Expand Up @@ -115,6 +138,11 @@ export function formatOpenResult(result: OpenResult): string {
if (result.hint) lines.push(` ${result.hint}`);
if (result.op) lines.push(` would send: ${JSON.stringify(result.op)}`);
break;
case "open_failed":
case "blocked_origin":
lines.push(c.red(result.hint ?? result.action));
if (result.url) lines.push(` ${result.url}`);
break;
case "engine_not_running":
lines.push(c.red("Summer Engine is not running (or no project is open) — nothing was opened."));
if (result.op) lines.push(` would send: ${JSON.stringify(result.op)}`);
Expand Down
7 changes: 7 additions & 0 deletions src/cli/commands/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { setupCommand } from "./setup.js";
// `summer setup` always ends with a doctor pass (network + engine probes);
// stub it so the command runs offline. `--print` already keeps the skills
// step in dry-run mode, so nothing else touches the machine.
// The default MCP channel consults npm latest; tests must not touch the network
// and must not depend on whether this checkout is ahead of the published tag.
vi.mock("../../installer/version-check.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../installer/version-check.js")>();
return { ...actual, resolveDefaultChannel: vi.fn(async () => ({ channel: "latest" })) };
});

vi.mock("../../core/capabilities/doctor.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../core/capabilities/doctor.js")>();
return {
Expand Down
7 changes: 6 additions & 1 deletion src/cli/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DoctorResult, printDoctorResult, runDoctor } from "../../core/capabilit
import { brandLine, c, sym, tildeify } from "../../core/format.js";

import { TOOLKIT_VERSION as cliVersion } from "../../core/version.js";
import { resolveDefaultChannel } from "../../installer/version-check.js";

const AGENT_LABEL: Record<SupportedAgent, string> = {
"claude-code": "Claude Code",
Expand Down Expand Up @@ -78,14 +79,18 @@ export const setupCommand = new Command("setup")
const agent = resolveAgentSelection(agentArg, opts.agent);
const scope = resolveConfigScope(opts.scope);

const defaultChannel = await resolveDefaultChannel(cliVersion);
const config = await configureAgentMcp({
agent,
scope,
dryRun: opts.dryRun,
print: opts.print,
localDev: Boolean(opts.localDev) || process.env.SUMMER_DEV === "1",
channel: opts.channel ?? process.env.SUMMER_CHANNEL,
channel: opts.channel ?? process.env.SUMMER_CHANNEL ?? defaultChannel.channel,
});
if (!opts.channel && !process.env.SUMMER_CHANNEL && defaultChannel.note && !opts.print) {
console.log(` ${c.dim(defaultChannel.note)}`);
}

const skills = setupSkills(agent, {
dryRun: Boolean(opts.dryRun || opts.print),
Expand Down
9 changes: 9 additions & 0 deletions src/cli/commands/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "../../core/skills-registry.js";
import { tildeify } from "../../core/format.js";
import { writeSkillMarker } from "../../installer/version-check.js";
import { pruneRetiredSkills } from "../../installer/legacy-skills.js";
import {
resolveInstallLocation,
resolveSkillAgent,
Expand Down Expand Up @@ -410,6 +411,14 @@ skillsCommand
// Keep this line's shape: setup tallies it (Installed|Updated|Generated <name> -> <path>).
console.log(` ${result.action} ${skill.name} -> ${result.path}`);
}
// Upgrades: with --force, remove the skills Summer installed in 2.8.x that
// v3 retired or renamed (summer-cloud, the un-prefixed vfx recipes, ...),
// so no host keeps a skill that points at removed tools.
if (opts.force && !name && (location.kind === "skill-dir" || location.kind === "opencode-skill-dir")) {
for (const pruned of pruneRetiredSkills(location.path, getSkillRegistry().map((entry) => entry.name))) {
console.log(` Removed ${pruned.name} -> ${pruned.path} (retired in 3.0.0)`);
}
}
if (name && skills[0]?.status === "preview") {
console.log(
` Note: ${name} is a preview skill — not yet exercised in-engine by the Summer team; its guidance says so.`
Expand Down
2 changes: 2 additions & 0 deletions src/core/capabilities/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,12 @@ async function checkCliVersionCurrent(): Promise<DoctorCheck> {
* recorded by `summer setup`: a `--local-dev` link gets the local-dev form
* so the fix does not replace the checkout with the published package. */
async function checkSkillsVersion(): Promise<DoctorCheck> {
const registry = await fetchLatestRegistryVersion();
const result = await buildSkillsVersionCheck({
installedCliVersion: version,
candidates: defaultSkillMarkerCandidates(),
recordedInstall: detectRecordedInstall,
...(registry.ok ? { latestRegistryVersion: registry.version } : {}),
});
return {
id: "skills-version-stale",
Expand Down
Loading
Loading